C++

15: 자원 관리 클래스에서 관리되는 자원은 외부에서 접근할 수 있도록 하자 자원 관리 객체를 쓰더라도 실제 자원을 사용해야 할 일이 더러 있다. int dayHeld(const Investment* pi) tr1::shared_ptr pInv(createInvestment()); iint days = daysHeld(pInv);// 에러 daysHeld 함수는 Investment* 타입의 객체 포인터를 원하지만 tr1::shared_ptr 타입의 객체를 넘기고 있기에 컴파일이 안 된다. RAII 클래스의 객체를 그 객체가 감싸고 있는 실제 자원으로 변환할 방법이 필요하다. 방법은 두 가지이다. 명시적 변환(explicit conversion) shared_ptr과 auto_ptr은 명시적 변환을 수행하..
14: 자원 관리 클래스의 복사 동작에 대해 진지하게 고찰하자 세상의 자원은 모두 힙에서 생성되지는 않는다. 힙에 생기지 않는 자원은 auto_ptr 혹은 shared_ptr 등의 스마트 포인터로 처리해 주기엔 맞지 않다는 것이 일반적인 견해이다. Mutex 타입의 뮤텍스 객체를 조작하는 C API를 사용하고 있다고 가정하자. 이 C API에서 제공하는 함수 중엔 lock 및 unlock이 있다. void lock(Mutex* pm); // pm이 가리키는 뮤텍스에 잠금을 건다. void unlock(Mutex* pm); // pm이 가리키는 뮤텍스의 잠금을 푼다. 이 뮤텍스 잠금을 관리하는 클래스를 하나 만들어 보자. 이전에 걸어 놓은 뮤텍스의 잠금을 잊지 않고 풀어 줄 목적이다. 이런 용도의 클래스는..
13: 자원 관리에는 객체가 그만! 프로그래밍 분야에서 자원이란, 사용을 일단 마치고 난 후엔 시스템에 돌려주어야 하는 모든 것이다. class Investment {}; Investment* createInvestment(); // Investment객체를 동적할당하고 그 포인터 반환 // 객체의 해제는 호출자 쪽에서 직접 해 void f() { Investment* *pInv = createInvestment(); // 팩토리 함수 호출 ... // pInv 사용 delete pInv; // 객체 해제 } 위의 코드는 멀쩡해 보이지만, 객체의 삭제에 실패할 수 있는 경우가 많다. delete에 도달 하기 전에 '...' 부분에서 return 문이 들어 있을 수 있다. createInvestment 호..
12: 객체의 모든 부분을 빠짐없이 복사하자 직접 복사 생성자와 복사 대입 연산자 같은 복사 함수를 직접 선언한다면 복사되는 객체가 갖고 있는 데이터를 빠짐없이 복사해야 한다. void logCall(const std::string& funcName); // 로그 기록내용 class Customer { public: Customer(const Customer& rhs) :name(rhs.name) { logCall("Customer copy constructor"); } Customer& operator=(const Customer& rhs) { logCall("Customer copy assignment operator"); name = rhs.name; return *this; } private: s..
11: operator=에서는 자기대입에 대한 처리가 빠지지 않도록 하자 a[i] = a[j]; // 자기대입 가능성이 크다 *px = *py;// 자기대입 가능성이 크다 위의 코드에서 i와 j가 같은 값이라면 자기대입, px와 py가 가리키는 대상이 같다면 자기대입이 된다. 이러한 자기대입이 생기는 이유는 여러 곳에서 하나의 객체를 참조하는 상태, 다시 말해 중복참조(aliasing)라고 불리는 것 때문이다. 대입 연산자는 우리가 신경쓰지 않아도 자기대입에 대해 안전하게 동작해야 한다. 동적 할당된 비트맵을 가리키는 원시 포인터를 데이터 멤버로 갖는 클래스를 하나 만들었다고 가정해 보자. class Bitmap{}; class Widget { public: Widget& operator=(const W..
10: 대입 연산자는 *this의 참조자를 반환하게 하자 C++의 기본제공 타입이나 표준 라이브러리에 속한 모든 타입에서는 대입 연산자가 좌변 인자에 대한 참조자를 반환하도록 구현되어 있다. 이것은 일종의 관례이니 지키는 것이 좋다. class Widget { public: Widget& operator=(const Widget& rhs) { ... return *this; // 좌변 객체의 참조자 반환 } Widget& operaotr+=(const Widget & rhs) { ... return *this; // 모든 형태의 대입 연산자에도 동일 } Widget& oeprator = (int rhs) { ... return *this; // 일반적이지 않은 경우에도 동일 } }; 단순 대입형 연산자 ..
09: 객체 생성 및 소멸 과정 중에는 절대로 가상 함수를 호출하지 말자 주식 거래를 본떠 만든 클래스 계통 구조가 있다고 가정하자. class Transaction { public: Transaction(); virtual void logTransaction() const = 0; }; Transaction::Transaction() { logTransaction(); // 기본 클래스 생성자 구현 } class BuyTransaction : public Transaction { public: virtual void logTransaction() const; }; BuyTransaction b; BuyTransaction 객체 b가 생성될 때 어떻게 될까? 파생 클래스 객체가 생성될 때 그 객체의 기본..
08: 예외가 소멸자를 떠나지 못하도록 붙들어 놓자 소멸자에서 예외가 발생한다면 정의되지 않은 동작을 보인다. 완전하지 못한 프로그램 종료나 미정의 동작의 원인은 예외가 터져 나오는 것을 내버려 두는 소멸자에게 있다. C++는 예외를 내보내는 소멸자는 좋아하지 않는다. class DBConnection { public: static DBConnection Create(); // DBConnection 객체 반환 void close(); // 연결을 닫는다. 이때 실패하면 예외 던짐 }; class DBConn { public: ~DBConn() // 데이터베이스 연결이 항상 닫히도록 { // 항상 챙겨주는 함수 db.close(); } private: DBConnection db; }; { DBConn ..
07: 다형성을 가진 기본 클래스에서는 소멸자를 반드시 가상 소멸자로 선언하자 class TimeKeeper { public: TimeKeeper(); ~TimeKeeper(); }; class AtomicClock : public TimeKeeper {}; class WaterClock : public TimeKeeper {}; class WristWatch : public TimeKeeper {}; TimeKeeper* getTimeKeeper(); // TimeKeeper에서 파생된 클래스를 // 통해 동적으로 할당된 객체의 // 포인터를 반환 TimeKeeper* ptk = getTimeKeeper(); ... delete ptk; TimeKeeper에서 파생된 클래스의 객체에 대한 포인터를 얻는..
06: 컴파일러가 만들어낸 함수가 필요 없으면 확실히 이들의 사용을 금해 버리자 클래스의 복사 생성자와 대입 연산자를 막고 싶다면 어떻게 해야 할까? 보통의 경우 특정한 기능을 지원하지 않았으면 하는 의도를 반영하는 방법은 그런 기능을 제공하는 함수를 선언하지 않는 것이다. 하지만 이 전략은 복사 생성자와 대입 연산자에 대해서는 불가하다. 왜냐하면 컴파일러가 자동적으로 선언해 버리기 때문이다. 해결 방법은 public 멤버로 두지 말고, 복사 생성자 및 대입 연산자를 private 멤버로 선언하는 것이다. 이렇게 하면 컴파일러가 자동적으로 기본 버전을 만들지 않고 외부로부터 호출할 수 없다. 하지만 멤버 함수나 friend 함수가 호출할 수 있다. 이것까지 막으려면, 정의를 하지 않으면 된다. 정의되지 ..
KANTAM
'C++' 카테고리의 글 목록 (5 Page)