C++의 규정에 의하면, 기본 클래스 포인터를 통해 파생 클래스 객체가 삭제될 때 그 기본 클래스에 비가상 소멸자가 들어 있으면 플그램 동작은 미정의 사항이라 되어 있다. 대게 그 객체의 파생 클래스 부분이 소멸되지 않게 된다.

가상 소멸자를 선언하는 것은 그 클래스에 가상 함수가 하나라도 들어 있는 경우에만 한정하자.


(이부분은 뭐라 정리하기 힘들다 이해는 했는데 내용 정리가 힘들다 추후 다시 정리 예정)


이것만은 잊지 말자!
- 다형성을 가진 기본 클래스에는 반드시 가상 소멸자를 선언해야 한다. 즉, 어떤 크랠스가 가상 함수를 하나라도 갖고 있으면, 이 클래스의 소멸자도 가상 소멸자이어야 한다.
-  기본 클래스로 설계되지 않았거나 다형성을 갖도록 설계되지 않는 클래스에는 가상 소멸자를 선언하지 말아야 한다.

관련 링크
http://ikpil.tistory.com/408
http://ikpil.tistory.com/296
http://www.kwak101.pe.kr/bbs/view.php?id=kwk_worksBBS&no=159
posted by deviAk
하나밖에 없는 클래스 즉, 똑같은게 없는 클래스를 만든다고 할 때 객체는 사본(copy)를 만드는 것 자체가 이치에 맞지 않다. 그러다 보니 객체를 복사하려 하는 코드는 컴파일이 되지 않게 하려면 어떻게 해야 할까?

HomeForSale h1;
HomeForSale h2;

HomeForSale h3(h1);       // h1을 복사하려 한다. - 컴파일이 되면 안된다.

h1 = h2;             // h2를 복사 하려 한다. - 컴파일이 되면 안된다.


해결 방법
- 컴파일러가 생성하는 복사 생성자와 복사 대입 연산자는 public 으로 자동 생성 해버리므로 이들을 private 멤버로 선언 하면 된다.
 ㄴ 효과1 : 클래스 멤버 함수가 명시적으로 선언되어 컴파일러는 자시느이 기본 버전을 만들 수 없다.
 ㄴ 효과2 : 비공개(private) 접근성을 가지므로 외부에서 호출 할 수 없게 된다.

- private 멤버 함수는 그 클래스의 멤버 함수 및 프렌드 함수가 호출 할 수 있으므로 함수 자체를 정의를 하지 않으면 된다.
 ㄴ 효과1 : 멤버 함수 및 프렌드 함수 까지의 접근을 완벽히 차단할 수 있다.

위의 꼼수는 [멤버 함수를 private 멤버로 서언하고 일부러 정의(구현)하지 않는 방법] 은 꽤 널리 퍼지면서 하나의 '기법' 화가 되어 C++의 iostream 라이브러리에 속한 몇몇 클래스에서도 복사 방지책으로 쓰이고 있다.

class HomeForSale {
public:
 ...
private:
 ...
 HomeForSale(const HomeForSale&);      // 선언만 되어 있다.
 HomeForSale& operator=(const HomeForSale);
}


이것만은 잊지 말자!
- 컴파일러에서 자동으로 제공하는 기능을 허용치 않으려면, 대응되는 멤버 함수를 private로 선언한 후에 구현은 하지 않은 채로 두자. Uncopyable과 비슷한 기본 클래스를 쓰는것도 한 방법이다.

관련링크
http://ikpil.tistory.com/407
http://yesarang.tistory.com/42
http://ikpil.tistory.com/318
posted by deviAk
복사 생성자, 복사 대입 연산자, 생성자, 소멸자 는 사용자가 선언을 하지 않아도 컴파일러가 자동으로 public inline 함수로 선언해 버린다.

calss Empty{};


class Empty {
public:
Empty() { ... }                 // 기본 생성자
Empty(const Empty& rhs) { ... }          // 복사 생성자
~Empty() { ... }                // 소멸자

Empty& operator= (const Empty& rhs) { ... }    // 복사 대입 연산자


위의 두 클래스는 같다고 보면 된다.

참조
1. 복사 생성자를 제외한 생성자를 선언하면 컴파일러가 기본 생성자는 만들지 않는다.

이것만은 잊지 말자!
- 컴파일러는 경우에 따라 클래스에 대해 기본 생성자, 복사 생성자, 복사 대입 연산자, 소멸자를 암시적으로 만들어 놓을 수 있다.

관련링크
http://ikpil.tistory.com/406
http://zeniroy.springnote.com/pages/17563
http://chiarang.egloos.com/1685406
http://ikpil.tistory.com/346
http://redinlife.egloos.com/1611549
http://chiarang.egloos.com/1685433
http://chiarang.egloos.com/1685521
http://babonamu.egloos.com/1090073


posted by deviAk
초기화되지 않은 값을 읽도록 내버려 두면 정의되지 않은 동작이 그대로 흘러 나오게 된다.

모든 객체를 사용하기 전에 항상 초기화 하자! 기본제공 타입으로 만들어진 비멤버 객체에 대해서는 초기화를 손수 해야 한다.

int x = 0;      // int의 직접 초기화

const char* text = "A C-style string"; // 포인터의 직접 초기화

double d;     // 입력 스트림에서 읽음으로써
std::cin >> d;   // "초기화" 수행

이런 부분을 제외하고 나면, C++의 초기화의 나머지 부분은 생성자로 귀결된다. 생성자에서 지킬 규칙은 간단하다. 그 객체의 모든 것을 초기화하자! 단 대입(assignment)을 초기화(initialization)와 헷갈리지 말자!

class PhoneNumber { ... };

class ABEntry {
public:
 ABEntry(const std::string& name, const std::string& address,
     const std::list<PhoneNumber>& phones);
private:
 std::string theName;
 std::string theAddress;
 std::list<phoneNumber> thePhones;
 int numTimesConsulted;
};

ABEntry::ABEntry(const std::string& name, const std::string& address,
        const std::list<PhoneNumber>& phones)
{
 theName = name;       // 이것은 초기화가 아닌 '대입' 이다.
 theAddress = adress;
 thePhones = phones;
 numTimesConsulted = 0;

}


ABEntry::ABEntry(const std::string& name, const std::string& address,
        const std::list<PhoneNumber>& phones)
 : theName(name),       // 이것이 바로 '초기화' 이다.
 theAddress(adress),
 thePhones(phones),
 numTimesConsulted(0)

{}               // 생성자에는 아무것도 없다.


C++ 객체를 구성하는 데이터의 초기화 순서

1. 기본 클래스는 파생 클래스보다 먼저 초기화된다.
2. 클래스 데이터 멤버는 그들이 선언된 순서대로 초기화 된다.


정적 객체(static object) 의 종류

1. 전역 객체.
2. 네임스페이스 유효범위에서 정의된 객체.
3. 클래스 안에서 static으로 선언된 객체.
4. 함수 안에서 static으로 선언된 객체.
5. 파일 유효범위에서 static으로 정의된 객체

정적 객체(static object) 의 주의점

1. 이중에서 함수 안에 있는 객체는 지역 정적 객체(local static object)라고 하고, 나머지는 비지역 정적 객체(non-local static object)라고 한다. 정적 객체는 프로그램이 끝날 떄 자동으로 소멸된다.

2. 별개의 지역에서 정의된 비지역 정적 객체들의 초기화 순서는 정해져 있지 않으므로 주의 하자.


이것만은 잊지 말자!
- 기본제공 타입의 객체는 직접 손으로 초기화 한다. 경우에 따라 저절로 되기도 하고 안되기도 하기 떄문이다.
- 생성자에서는 데이터 멤버에 대한 대입문을 생성자 본문 내부에 넣는 방법으로 멤버를 초기화 하지 말고 멤버 초기화 리스트를 즐겨 사용하자. 그리고 초기화 리스트에 데이터 멤버르 ㄹ나열할 떄는 클래스에 각 데이터 멤버가 선언된 순서와 똑같이 나열하자.
- 여러 번역 단위에 있는 비지역 정적 객체들의 초기화 순서 문제는 피해서 설계해야 한다. 비지역 정적 객체를 지역 정적 객체로 바꾸면 된다.

관련링크
http://ikpil.tistory.com/405
posted by deviAk

const 사용처

클래스 바깥에서는 전역 혹은 네임스페이스 유효범위의 상수를 선언(정의)하는 데 쓸 수 있다.
파일, 함수, 블록 유효범위에서 static으로 선언한 객체에도 const를 붙일 수 있다.
클래스 내부의 경우 정적 멤버 및 비정적 데이터 멤버 모두를 상수로 선언할 수 있다.

char greeting[] = "Hello";

char  *p = greeting;         // 비상수 포인터, 비상수 데이터
const char *p = greeting;       // 비상수 포인터, 상수 데이터
char * const p = greeting;      // 상수 포인터, 비상수 데이터
const char * const p = greeting;   // 상수 포인터, 상수 데이터

void f1(const Widget *pw);   // f1은 상수 Widget 객체에 대한 포인터를 매개 변수로 취한다.
void f2(Widget const *pw);   // f2도 f1과 동일하다.

std::vector<int> vec;
...
const std::vector<int>::iterator iter = vec.begin();
                 // iter는 T* const처럼 동작한다.
*iter = 10;             // OK, iter가 가르키는 대상을 변경한다.
++iter;              // Error! iter는 상수이다.

std::vector<int>::const_iterator cIter = vec.begin();
                 // cIter는 const T*처럼 동작한다.
*cIter = 10;            // Error! *cIter가 상수이다.
++cIter;               // OK, 문제 없다.

이것만은 잊지 말자!
- const를 분텨 선언하면 컴파일러가 사용상의 에러를 잡아내는데 도움을 준다. const는 어떤 유효범위에 있는 개체에도 붙을 수 있으며, 함수 매개변수 밑 반환 타입에도 붙을 수 있으며, 멤버 함수에도 붙을 수 있다.
- 컴파일러 쪽에서 보면 비트수준 상수성을 지켜야 하지만, 개념적인(논리적인) 상수성을 사용해서 프로그래밍해야 한다.
- 상수 멤버 및 비상수 멤버 함수가 기능적으로 서로 똑같게 구현되어 있을 경우에는 코드 중복을 피하는 것이 좋은데, 이때 비상수 버전이 상수 버전을 호출하도록 만든다.

관련링크
http://ikpil.tistory.com/402
posted by deviAk