내용
객체의 안쪽 부분을 캡슐화한 객체 지향 시스템 중 설계가 잘 된 것들을 보면, 객체를 복사 하는 함수가 딱 두개만 있는 것을 볼 수 있다. 이 둘을 복사 생성자와 복사 대입 연산자라 하고, 이 둘을 통틀어 객체 복사 함수(copying function)라 부른다.

컴파일러가 생성한 복사 함수는 기본적인 요구에 충실하다. 복사되는 객체가 갖고 있는 데이터를 빠짐없이 복사한다.

주의점
만약 컴파일러가 만든 기본 동작에 마음에 안들어 직접 복사 함수를 선언한다면 다음과 같은 것들을 지켜야 한다.
  1. 기존 클래스에 멤버를 추가하면 복사 함수를 수정 해줘야 한다.
  2. 파생 클래스에서 기본 클래스의 복사 함수를 호출 하도록 만들어야 한다.
    CBase { ... }
    
    CTest
    {
     private:
      INT m_iMember;
    }
    
    CTest::CTest(const CTest& rhs)
    : CBase(rhs),                     // 기본 클래스의 복사 생성자를 호출한다.
     m_iMember(rhs.m_iMember)
    {
    }
    
    CTest& CTYest::operator=(const CTest&rhs)
    { 
     CBase::operator=(rhs);
     m_iMember = rhs.m_iMember;      // 기본 클래스 부분을 대입한다.
     
     return *this;
    }
    


이것만은 잊지 말자!
- 객체 복사 함수는 주어진 객체의 모든 데이터 멤버 및 모든 기본 클래스 부분을 빠드리지 말고 복사해야 한다.
- 클래스의 복사 함수 두개를 구현할 떄, 한쪽을 이용해서 다른 쪽을 구현하려는 시도는 절대로 하지 말자.그 대신, 공통된 동작을 제 3의 함수에다 분리해 놓고 양쪽에서 이것을 호출하게 만들어서 해결하자.

관련링크
http://ikpil.tistory.com/414
http://kldp.org/node/78631
http://rookiecj.tistory.com/8
http://ikpil.tistory.com/298

posted by deviAk

내용
자기대입(self assignment) : 어떤 객체가 자기 자신에 대해 대입 연산자를 적용 하는 것을 말한다.

a[i] = a[j]; 또는 *px = *py; 는 자기대입의 가능성을 가지고 있는 문장이다. 어뜻 보기에 명확하지 않은 이러한 자기대입이 생기는 이유는 여러 곳에서 하나의 객체를 참조하는 상태, 즉 중복참조(aliasing)라고 불리는 것 때문이다.

그렇기 때문에, 같은 타입으로 만들어진 객체 여러개를 참조자 혹은 포인터로 물어 놓고 동작하는 코드를 작성할 떄는 같은 객체가 사용 될 가능성을 고려 하는것이 일반적으로 바람직한 자세이다.

해결법
  1. operator=의 첫머리에서 일치성 검사(identity test)를 통해 자기대입을 점검한다.
    자기대입의 경우 많이 일어나는 것이 아니기 때문에 모든 객체에 대해서 비교하는 것은 효율이 낮다.
  2. class CEx { ... };
    
    CTest& CTest::operator=(const CTest& rhs)
    {
     if ( this == &rhs) return *this; // 객체가 같은지, 즉 자기대입인지 검사한다.
                                      // 자기대입이면 아무것도 안한다.
    delete pMember;
    pMember = new CEx(*rhs.pMember);
    
    return *this;
    }
    
  3. 문장의 순서를 적절히 조정한다.
  4. class CEx { ... };
    
    CTest& CTest::operator=(const CTest& rhs)
    {
     CEx *pOrig = pMember;           // 원래의 pMember를 어딘가에 기억해 둔다.
     pMember = new CEx(*rhs.pMember);// 다음, pMember가 *pMember의 사본을 가르키게 만든다.
     delete pOrig;                   // 원래의 pMember를 삭제한다.
    
     return *this;
    }
    
  5. 복사 후 맞바꾸기(copy and swap) 기법을 사용한다.
  6. class CTest {
     ...
     void swap(CTest& rhs);   // *this의 데이터 및 rhs의 데이터를 맞바꾼다.
     ...
    };
    
    CTest& CTest::operator=(const CTest& rhs)
    {
     CTest temp(rhs);         // rhs의 데이터에 대한 사본을 하나 만든다.
    
     swap(temp);              // *this의 데이터를 그 사본의 것과 맞바꾼다.
     
    return *this;
    }
    



이것만은 잊지 말자!
- operator=을 구현할 때, 어떤 객체가 그 자신에 대입되는 경우를 제대로 처리하도록 만들자. 원본 객체와 복사대상 객체의 주소를 비교해도 되고, 문장의 순서를 적절히 조정할 수도 있으며, 복사 후 맞바꾸기 기법을 써도 된다.
- 두 개 이상의 객체에 대해 동작하는 함수가 있다면, 이 함수에 넘겨지는 객체들이 사실 같은 객체인 경우 정확하게 동작하는지 확인해 보자.


관련링크
http://ikpil.tistory.com/413
http://jangyeol.springnote.com/pages/348598.xhtml
http://evax.springnote.com/pages/871438
http://ilu8318.egloos.com/1705010
http://ikpil.tistory.com/299

posted by deviAk

이유
일종의 관례이므로 관례를 지키는 것이 좋다.


내용
C++의 대입 연산은  x = y = z = 15; 처럼 여러 개가 사슬 처럼 엮일 수 있다.
이처럼 대입 연산이 사슬처럼 엮이려면 대입 연산잔가 좌변 인자에 대한 참조자를 반환하도록 구현이 되어 있다.
이런 구현은 일종의 관례(convention)인데, 나름대로 만드는 클래스에 대입 연산자가 혹 들어간다면 이 관례를 지키는 것이 좋다.


이것만은 잊지 말자!
- 대입 연산자는 *this의 참조자를 반환하도록 만들자.


관련링크
http://ikpil.tistory.com/412
http://redinlife.egloos.com/1604282

posted by deviAk

이유
1. 호출한 결과가 원하는 대로 돌아가지 않을 것이다.
2. 제대로 돌아간다 해도 폭탄을 가지고 있는 것과 같다.

설명
파생 클래스 객체가 생성될 때 그 객체의 기본 클래스 부분이 파생 클래스 부분보다 먼저 호출된다.  그렇기에 기본 클래스의 생성자가 호출될 동안에는, 가상 함수는 절대로 파생 클래스 쪽으로 내려가지 않는다.

기본 클래스 생성자는 파생 클래스 생성자보다 먼저 실행되기 때문에, 기본 클래스 생성자가 돌아가고 있을 때 파생 클래스 데이터 멤버는 아직 초기화 된 상태가 아닌 것이 핵심이다.

객체가 소멸 될 때는 파생 클래스의 소멸자가 일단 호출되고 파생 클래스만의 데이터 멤버는 정의되지 않은 값으로 가정하기 때문에, C++은 이들을 없는 것처럼 취급하고 진행한다. 기본 클래스 소멸자에 진입할 떄 객체는 기본 클래스 객체가 되며, 모든 C++ 기능들 (가상 함수, dynamic_cast, 기타 등등) 역시 기본 클래스 객체의 자격으로 처리한다.

참조
상속관계의 클래스 호출 순서는 다음과 같다.

1. 클래스 생성 시
  1. 기본 클래스 생성자 호출 후 기본 클래스 멤버 객체 초기화
  2. 파생 클래스 생성자 호출 후 파생 클래스 멤버 객체 초기화
2. 클래스 소멸 시
  1. 파생 클래스 소멸자 호출 후 파생 클래스 멤버 객체 소멸
  2. 기본 클래스 소멸자 호출 후 기본 클래스 멤버 객체 소멸

이것만은 잊지 말자!
- 생성자 혹은 소멸자 안에서 가상 함수를 호출하지 말자. 가상 함수라고 해도, 지금 실행 중인 생성자나 소멸자에 해당되는 클래스의 파생 클래스 쪽으로는 내려가지 않는다.

관련링크
http://ikpil.tistory.com/410
http://evax.springnote.com/pages/871402
http://ljh131.tistory.com/16

posted by deviAk
class DBConnection {
public:
  static DBConnection create();
  void close();};

위의 DBConnection 객체에 대해 사용자가 cloase를 직접 호출해야 하는 설계이다. 사용자의 망각을 사전에 차단하는 좋은 방법이라면 DBConnection에 대한 자원관리 클래스를 만들어서 그 클래스의 소멸자에서 close를 호출하게 만드는 것이다.
class DBConn {
      // DBConnection 객체를 관리하는 클래스
public:
 ...
 ~DBConn()        // 데이터베이스 연결이 항상 닫히도록 확실히 챙겨주는 함수
 {
  db.cloase();
 }
private:
 DBConnection db;
};

위의 두 클래스를 활용하여 다음과 같은 프로그래밍이 가능해진다.
{                    // 블록 시작
 DBConn dbc(DBConnection::create());  // DBConneciton 객체를 생성하고
                                         // 이것을 DBConn 객체로
                    // 넘겨서 관리를 맡긴다.
 ...   // DBConn 인터페이스를 통해 DBConnection 객체를 사용한다.
}     // 블록 끝. DBConn 객체가 여기서 소멸된다. 
      // 따라서 dBConnection 객체에 대한 close 함수의 호출이
           // 자동으로 이루어진다.

close 호출만 일사천리로 성공하면 아무 문제될 것이 없는 코드 이지만 만약 close 호출햇는데 여기서 예외가 발생했다고 가정하면 DBConn의 소멸자는 분명히 이 예외를 전파할 것이다.

class DBConn {
public:
 ...
 void close()
 {
  db.close();
  closed = true;
 }
 ~DBConn()
 {
  if (!closed)
  try {
   db.close();
  }
  catch (...) {
   close 호출이 실패 했다는 로그를 작성한다.
   ...
  }
 }
private:
 DBConnection db;
 bool closed;
};


어떤 동작이 예외를 일으키면서 실패할 가능성이 있고 또 그 예외를 처리해야 할 필요가 있다면, 그 예외는 소멸자가 아닌 다른 함수에서 비롯된 것이어야 한다는 것이 포인트 이다.


이것만은 잊지 말자!
- 소멸자에서는 예외가 빠져나가면 안된다. 만약 소멸자 안에서 호출된 함수가 예외를 던질 가능성이 있다면, 어떤 예외든지 소멸자에서 모두 받아낸 후에 삼켜버리든지 프로그램을 끝내든지 해야 한다.,

- 어떤 클래스의 연산이 진행되다가 던진 예외에 대해 사용자가 반응해야 할 필요가 있다면, 해당 연산을 제공하는 함수는 반드시 보통의 함수),즉 소멸자가 아닌 함수)이어야 한다.

관련 링크
http://ikpil.tistory.com/409
http://ikpil.tistory.com/365
http://redinlife.egloos.com/1627105
http://ilu8318.egloos.com/1705005
http://flashcafe.org/bbs/board.php?bo_table=programming_study&wr_id=83
posted by deviAk