Programming/C++

[Effective C++] Chapter 2. 생성자, 소멸자 및 대입 연산자(1)

며용 2022. 6. 12. 19:28

항목 5: C++가 은근슬쩍 만들어 호출해 버리는 함수들에 촉각을 세우자

 

빈 클래스라도 컴파일러가 자동으로 생성해주는 멤버함수

  • 복사생성자 외 생성자 선언 안했을 경우
    • 기본 생성자
  • 따로 정의하지 않은 경우
    • 복사 생성자
    • 복사 대입 연산자
      복사는 데이터 사본을 그대로 복사함 (얕은 복사)
    • 소멸자

==> 컴파일러가 자동 생성하는 함수가 있음을 인지하고 직접 선언하여 명확히 처리하자

class CClass
{
private:
    char* name;
public:
    //기본 생성자
    CClass() {}
    //복사 생성자 (동적할당 얕은 복사 주의)
    CClass(const CClass &class) {}
    //복사 대입 연산자 (동적할당 얕은 복사 주의)
    CClass& operator=(const Class &class) {}
}

int main()
{
    CClass class; //기본
    CClass copy(class); //복사
    CClass temp = class; //복사, 생성 때 같은 타입을 대입 연산하면 복사 생성자 호출함
    class = copy; //대입, 이미 생성되어 있는 객체에 값 복사할 때 사용됨
}

 

해당 내용은 C++98까지 이고 C++11에서는 자동 생성되는 함수들이 추가 되었다

  • 이동 생성자
  • 이동 대입 연산자

==> 성능이랑 불필요한 복사를 최소화해주기 때문에 미리 알아두는게 좋다.

 

 

얕은 복사?

포인트 변수는 주솟값을 담을 수 있는 변수로 복사생성자를 따로 정의하지 않으면 주소값만 복사가 됨

--> 동일한 주소를 가리키는 인스턴스가 생기게 된다

--> 주소값만 복사했을 경우 해당 주소가 해제되어도 포인터는 그대로 해당 주소를 가리키고 있다.

      (메모리가 해제된 곳을 가리키고 있는 포인터를 dangling pointer라 함)

      => 이걸 막기 위해서 shared_ptr 같은 별도의 매니저 단의 클래스를 사용한다 (or memcopy 같은 걸 써서 직접 복사해야함)

 

class Person{
    int age;
    char* name;

    Person(int _age, const char* _name)
    {
    	age = _age;
        name = new char[strlen(_name) + 1];
        strcpy(name, _name);
    }
    
    //복사생성자; 깊은 복사
    Person(const Person& person)
    {
    	age = person.age;
        name = new char[strlen(person.name) + 1];
        strcpy(name, person.name)
    }
}

 


항목 6: 컴파일러가 만들어낸 함수가 필요 없으면 확실히 이들의 사용을 금해 버리자

 

컴파일러가 자동으로 생성하는 public 멤버함수가 불필요한 경우, 링크/컴파일 에러를 발생시켜 확실하게 막자

ex) 세상에 똑같은 자동차가 두 개 존재할 수 없음 / 동일한 집 매물이 두 개 존재할 수 없음 --> 복사 생성자 막기

 

컴파일러가 자동 생성하는 복사 관련 함수 막는 방법

  • 링크 단계 방법: private으로 복사생성자/복사대입연산자 선언 후 정의하지 않기
  • 컴파일 단계 방법: 복사 관련 함수가 private으로 선언된 클래스를 만들어 상속받기
class CUncopyable
{
public:

private:
    //정의 X --> 내부 호출 시 undefined 링크 에러
    //선언에서는 매개변수 이름도 필요 없음 (type과 identifirer만 컴파일러에게 알려주는 것이기 때문)
    CUncopyable(const CUncopyable&); 
    CUncopyable& opertator=(const CUncopyable&); 
};

//public/protected/private이든 derived class의 멤버들은 base class의 private 멤버에 접근 자체가 불가능함 --> 컴파일 에러 발생
class CTemp : private CUncopyable
{
public:

//CTemp의 멤버함수나 friend 함수에서 copy를 하려고 하면 CTemp에는 복사 관련 함수가 없으니 생성하려 할텐데
//이때 컴파일러는 base class의 대응 버전을 호출하게 되어있음
}

 

클래스를 private으로 상속받는 케이스는 잘 없다

private / public / protected 를 정확히 지켜주며 개발해야 함