본문 바로가기

Effective C++/2. 생성자, 소멸자 및 대입 연산자

(8)
항목 12: 객체의 모든 부분을 빠짐없이 복사하자 애초에 Customer의 기본생성자가 없으면 에러남 왜 why, 어찌됐든 PriorityCustomer의 복사 생성자 호출 전에 Customer의 생성자가 호출될 탠데, PriorityCustomer 복사 생성자의 맴버 초기화 리스트에 기본 클래스 생성자에 넘길 인자들도 명시되지 않음 => 기본 클래스의 기본 생성자를 호출해야 하는데, Custom 클래스에는 기본 생성자가 없다. => Error 그리고 기본 생성자를 만들어도 이 기본 생성자는 복사가 아닌, 기본적인 초기화를 해줄 것인데 의미가 있나(인자가 없으니 당연한 것임) 그래서 만들어주자 그냥 복사함수에서는 모든 부분을 복사하자!!!!!! 코드중복을 피하고 싶은 변태들은 복사 대입연산자에서 복사 생성자를 호출하는 그런 방법을 떠올리는데 말이 안된다..
항목 11: operator=에서는 자기대입에 대한 처리가 빠지지 않도록 하자 자기대입 = self assignment a=a; a[i] = a[j]; // i==j일시 자기대입 *pa = *pb; // pa와 pb가 같은 객체를 가르킬 시 자기대입 void doSomething(const Base& pb, Derived* pd) // pb와 pd가 같은 객체를 가르킬 가능성이 있음 모두 다 자기 대입의 가능성이 있거나, 자기 대입임 중복참조(aliasing) == 여러곳에서 하나의 객체를 가르킴 => 자기대입 가능성 참고) 왜 this == &t 를 비교하냐? 만약 t = t; 이렇게 되있으면(Self Copy) 저게 없으면 t를 지워버리고 다시 할당받을것을 가르키는데 문제는 복사할 구조체도 같이 없어지기 때문에 복사가 안됨 >> Self Copy이면 바로 Return 해버림 A..
항목 10: 대입 연자는 *this의 참조자를 반환하게 하자 수식 => 값이 된다. 대입 연산의 결과 : 해당 변수에 저장된 값 우리가 만드는 클래스에서 대입 연산자가 들어간다면 이 규칙을 무조건 지켜야됨 x = y = z = 15 이면 처음에 z = 15, 그 다음 y = z, 그 다음 x = y 이다. 그럼 y=z에서 z는 갱신된 z이다.(z=15를 완료한) => 대입 연산자가 좌변 인자에 대한 참조자를 반환하도록 구현 대입 연산자는 *this의 참조자를 반환하도록 만들자.
항목 9: 객체 생성 및 소멸 과정 중에는 절대로 가상함수를 호출하지 말자 주식 거래를 본떠 만든 클래스 계통 구조가 있다고 하자. 매도, 매수 등등이 있어야 할 것인데 이러한 거래를 모델링할 때 꼭 감사(audit)기능이 있어야 한다. 그래서 어떤 주식 거래 객체가 생성될 때 마다 감사 로그에 적 절한 거래 내역이 나오게 하고 싶다. 그래서 다음과 같이 코드를 짰음. BuyTransaction의 생성자가 호출될 탠데, 생성자의 호출 순서는 base 클래스의 생성자가 먼저 호출되고 그 다음 derived 생성자가 호출되고 … 순서이다. base 클래스 생성자에 가상함수인 logTransaction을 호출하는 부분이 보이는데, 이때의 logTransaction은 BuyTransaction가 아니라 Transaction의 것이다. 기본 클래스의 생성자가 호출될 동안에는 가상 함수는..
항목 8: 예외가 소멸자를 떠나지 못하도록 붙들어 놓자 예외가 터져 나오는 것을 내버려두는 소멸자 ==> 프로그램 강제종료 또는 미정의 동작의 원인 만약 예외를 던졌는데 예외처리에 실패할 수 있는 코드를 소멸자에 넣아야 한다면 어떻게 함?? 데이터베이스 연결을 나타내는 클래스를 쓰고 있다고 가정해보자. 보다싶이 사용자가 DBConnection 객체에 대해 close를 직접 호출해야하는 설계이다. 만약 사용자의 망각을 사전에 차단하는 좋은 방법이라면 DBConnection에 대한 자원 관리 클래스를 따로 만들어서 그 클래스의 소멸자에서 close()를 호출하게 하는 것이다. 3장에서 이런 자원 관리 클래스를 충분히 다루고 있지만, 대충 살펴보면 이 때 close 호출만 일사천리로 성공하면 아무 문제될 것이 없다. 하지만 close()에서 예외가 발생하면??? ..
항목 7: 다형성을 가진 클래스에서는 소멸자를 반드시 가상 소멸자로 선언하자 시간 기록을 유지하는 방법은 활용에 따라 무궁무진 하다. 그래서 TimeKeeper 라는 기본 클래스를 만들어 놓은 후 적절한 용도에 따라 이것을 상속하도록 설계하면 딱 알맞을 거 같다. TimeKeeper를 쓰는 사용자들은 시간 정보에 접근하고 싶은데, TimeKeeper에서 시간 계산을 어떻게 하는지는 관심이 없고, 그냥 시간 정보에 접근만 하고 싶다. => 이럴 때 어떤 시간기록 객체에 대한 포인터를 반환하는 용도로 팩토리 함수(factory function, 새로 생성된 derived class 객체에 대한 base class 포인터를 반환하는 함수) 를 만들어 두면 딱 좋을 거 같다.(getTimeKeeper()) [C++] 팩토리 함수 :: IT's me (tistory.com) 참고 팩토리 ..
항목 6: 컴파일러가 만들어낸 함수가 필요없으면 확실히 이들의 사용을 금해 버리자. 부동산 거래 프로그램을 만든다. 그래서 class HomeforSale을 만드는데, 부동산 중개 업자가 꼭 신신당부한 말이 있다. 모든 자산은 세상에 하나밖에 없다!! 그럼 HomeForSale은 사본자체를 만들 수 없게 해야 한다. 그러다 보니 HomeForSale 객체를 복사하려 하면 컴파일 에러가 뜨게 하고 싶다. 일반적으로 어떤 클래스에서 특정한 종류의 기능을 지원하지 않았으면 하는 의도를 반영할려면, 그런 기능을 제공하는 함수를 선언하지 않으면 되는데, 복사 생성자와 복사 대입 연산자의 경우는 선언하지 않으면 컴파일러가 자동으로 선언해버리는게 문제다. 해결의 열쇠 : 기본 복사 생성자, 기본 복사 대입 연산자는 public member로 선언된다!! => private으로 바꿔버리면 됨 => 외..
항목 5: C++가 은근슬쩍 만들어 호출해 버리는 함수들에 촉각을 세우자. 객체지향 C++에서 클래스 안에 직접 선언해 넣지 않으면 컴파일러가 저절로 선언해 주도록 하는 맴버 함수들 이 있다. copy constructor copy assignment opeartor destructor default constructor 이때 이 맴버함수들은 모두 기본형, public, inline 함수이다. 컴파일러가 만들어주는 이 함수들은 무슨역할을 할까? => 컴파일러에게 "배후의 코드"를 깔 수 있는 자리를 마련하는 것이다. 배후의 코드 = base class 및 non-static member의 생성자, 소멸자를 호출하는 코드 이 때 소멸자는 derived class 가 상속한 base class의 소멸자가 virtual로 되있지 않으면 역시 non-virtual 소멸자로 만들어진다는..