[Effective C++] 03. C++가 은근슬쩍 만들어 호출해 버리는 함수들에 촉각을 세우자

2025. 11. 28. 10:49·C++/[서적] Effective C++

개요

빈 클래스의 경우, 상황에 따라 컴파일러는 기본 생성자 / 소멸자 / 복사 생성자 / 복사 대입 연산자를 public, inline으로 선언해 놓는다. 어떤 상황에서 자동으로 생성되는지 살펴보자.

// 자동 생성 예시
class Empty{
public:
    // 생성자
    Empty() { }
    
    // 소멸자 : 가상 여부에 따라 다름
    ~Empty() { }
    
    // 복사 생성자
    Empty(const Empty& rhs) { }
    
    // 복사 대입 연산자
    Empty& operator=(const Empty& rhs) { }
};

 


 

1. 기본 생성자 / 소멸자

Empty e1; // 기본 생성자, 소멸자 선언

클래스의 인스턴스를 생성하는 경우 해당 클래스에 생성자, 소멸자가 선언되어 있지 않으면 컴파일러는 자동으로 기본 생성자와 소멸자를 public, inline으로 선언한다.

소멸자의 경우 클래스가 상속한 기본 클래스의 소멸자가 가상 소멸자로 되어 있지 않으면 비가상 소멸자로 만들어진다는 점을 유의하자.

 


 

2. 복사 생성자

Empty e2(e1); // 복사 생성자 선언

클래스의 인스턴스를 복사 생성자를 통해 초기화하는 경우 해당 클래스에 복사 생성자가 선언되어 있지 않으면 컴파일러는 자동으로 복사 생성자를 public, inline으로 선언한다.

이때 복사 생성자는 원본 객체의 비정적 데이터를 사본 객체 쪽으로 복사하는 일을 한다.

세부적인 작동원리를 살펴보자.

// NamedObject.h
template<typename T>
class NamedObject {
public:
    NamedObject(char* name, const T& value);
    NamedObject(string& name, const T& value);
    
    // 암시적으로 선언된 복사 생성자
    NamedObject(const NamedObject& rhs)
    : nameValue(rhs.nameValue), objectValue(rhs.objectValue)
    { }
    
private:
    string nameValue;
    T objectValue;
};

// main.cpp
NamedObject<int> no1("Smallest Prime Number", 2);
NamedObject<int> no2(no1); // 복사 생성자 선언 및 호출

no1.nameValue, no1.objectValue를 no2에 복사하는 과정에서, no2.nameValue의 초기화는 string의 복사 생성자에 no1.nameValue를 인자로 넘겨 호출함으로써 이루어진다. 반면에, objectValue는 기본제공 타입인 int이므로 복사 생성자 호출 없이 no1.objectValue의 각 비트를 그대로 복사해오는 것으로 끝난다.

자동으로 선언된 복사 생성자의 동작 방식을 보면, 초기화 리스트를 활용하여 초기화를 진행하고 있기 때문에 nameValue, objectValue가 const 혹은 참조형태여도 문제가 발생하지 않는다.

 


 

3. 복사 대입 연산자

Empty e1;
Empty e2;

e2 = e1; // 복사 대입 연산자 선언

복사 대입 연산자의 경우 복사 생성자와 다르게, 멤버 변수가 참조 혹은 const 형태인 경우 문제가 발생한다.

// NamedObject.h
template<typename T>
class NamedObject {
public:
    NamedObject(string& name, const T& value);
    
    // 암시적으로 선언된 복사 대입 연산자
    NamedObject& operator=(const NamedObject& rhs)
    { 
        nameValue = rhs.nameValue; // string의 복사 대입 연산자 호출
        objectValue = rhs.objectValue;
        return *this;
    }
    
private:
    string& nameValue;   // 참조 형태
    const T objectValue; // const
};

// main.cpp
NamedObject<int> no1("Smallest Prime Number", 2);
NamedObject<int> no2("Any Number", 10);

no1 = no2; // 컴파일 에러 발생

nameValue는 참조 형태이고, objectValue는 const 형 변수이다. 암시적으로 선언된 복사 대입 연산자의 작동 방식을 보면, no1.nameValue에 no2.nameValue를 대입하려고 하는 것을 볼 수 있다. 이는 참조 형태, const 형 변수인 경우 위배되는 방식이다. 따라서 복사 생성자와 달리 복사 대입 연산자에서는 멤버 변수가 참조 형태, const인 경우 컴파일 에러가 발생한다.

 


 

4. 암시적 생성 방지

(1) private 정의

class NoAutoCreate{
private:
    NoAutoCreate() { };
    ~NoAutoCreate() { };
    NoAutoCreate(const NoAutoCreate& rhs) { };
    NoAutoCreate& operator=(const NoAutoCreate& rhs) { };
};

private으로 생성자, 소멸자, 복사 생성자, 복사 대입 연산자를 선언하는 경우 컴파일러는 암시적으로 해당 함수들을 선언하지 않는다. 하지만 위의 경우 class 내부 혹은 friend 함수/클래스에서는 활용이 가능하므로 더 확실하게 차단하고자 한다면 정의를 배제하거나 다음과 같이 delete를 활용하여 작성하면 된다.


(2) delete

class NoAutoCreate{
public:
    NoAutoCreate() = delete;
    ~NoAutoCreate() = delete;
    NoAutoCreate(const NoAutoCreate& rhs) = delete;
    NoAutoCreate& operator=(const NoAutoCreate& rhs) = delete;
};

(3) Uncopyable 클래스 상속

class Uncopyable{
protected:
    Uncopyable() { }
    ~Uncopyable() { }
    
private:
    Uncopyable(const Uncopyable&);
    Uncopyable& operator=(const UnCopyable&);
};

class Something : private Uncopyable{
...
};

Uncopyable 클래스는 데이터 멤버가 전혀 없기 때문에 공백 기본 클래스 최적화(empty base class optimization, EBO) 기법이 먹혀 들어갈 여지가 있다.하지만, Uncopyable을 상속받는 구조로 구현하게 될 경우 다중 상속으로 갈 가능성이 있으니 유의해야 한다. 부스트 라이브러리에도 Uncopyable과 똑같은 구실을 하는 클래스인 noncopyable이 있다고 하니 취향에 맞게 사용하면 될 것 같다.

'C++ > [서적] Effective C++' 카테고리의 다른 글

[Effective C++] 06. 대입 연산자는 *this의 참조자를 반환하게 하자  (0) 2025.11.28
[Effective C++] 05. 객체 생성 및 소멸 과정 중에는 절대로 가상 함수를 호출하지 말자  (0) 2025.11.28
[Effective C++] 04. 다형성을 가진 기본 클래스에서는 소멸자를 반드시 가상 소멸자로 선언하자  (0) 2025.11.28
[Effective C++] 02. 객체를 사용하기 전에 반드시 그 객체를 초기화하자  (0) 2025.11.28
[Effective C++] 01. #define을 쓰려거든 const, enum, inline을 떠올리자  (1) 2025.11.26
'C++/[서적] Effective C++' 카테고리의 다른 글
  • [Effective C++] 05. 객체 생성 및 소멸 과정 중에는 절대로 가상 함수를 호출하지 말자
  • [Effective C++] 04. 다형성을 가진 기본 클래스에서는 소멸자를 반드시 가상 소멸자로 선언하자
  • [Effective C++] 02. 객체를 사용하기 전에 반드시 그 객체를 초기화하자
  • [Effective C++] 01. #define을 쓰려거든 const, enum, inline을 떠올리자
Meoyoung's Development Logs
Meoyoung's Development Logs
내가 보려고 만든 블로그
  • Meoyoung's Development Logs
    이게뭐영
    Meoyoung's Development Logs
  • 전체
    오늘
    어제
    • 분류 전체보기 (267) N
      • Unreal Engine 프로젝트 (16) N
        • 더 퍼스트 버서커 : 카잔 (16) N
        • The Entity Watcher (0)
      • 언리얼 엔진 (70)
        • 프레임워크 (2)
        • GAS (12)
        • 꿀 Tip ! (7)
        • 트러블슈팅 (27)
        • 캐릭터 (2)
        • VR (1)
        • Lighting (2)
        • 멀티스레드 (2)
      • C++ (31)
        • 문법 정리 (8)
        • [서적] Fundamental C++ 프로그래밍 .. (5)
        • [서적] 이것이 C++이다 (11)
        • [서적] Effective C++ (7)
      • 게임잼 (3)
      • 강의 (36)
        • [강의] 이득우의 언리얼 프로그래밍 Part1 (13)
        • [강의] 이득우의 언리얼 프로그래밍 Part2 (2)
        • [강의] 이득우의 언리얼 프로그래밍 Part3 (12)
        • [강의] 소울라이크 개발 A-Z (4)
        • [강의] Udemy-2D (5)
      • C# (1)
        • [서적] 이것이 C#이다 (1)
      • 코딩테스트 (26)
        • 프로그래머스 (6)
        • 알고리듬 (13)
        • 자료구조 (7)
      • 컴퓨터 과학 (27)
        • 운영체제 (11)
        • 데이터베이스 (0)
        • 디자인패턴 (0)
        • 자료구조 (5)
        • 네트워크 (0)
        • 컴퓨터구조 (11)
      • 면접준비 (0)
        • C++ (0)
        • 운영체제 (0)
        • 자료구조 (0)
      • 기타 (48)
        • [팀프로젝트] The Fourth Descenda.. (5)
        • GetOutOf (15)
        • [개인프로젝트] FPS 구현 맛보기 (5)
        • [서적] 인생 언리얼5 (4)
        • 스파르타코딩클럽 (15)
        • 객체지향프로그래밍 (2)
        • 컴퓨터회로 (2)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    셸정렬
    쉘정렬
    참가후기
    선택정렬
    알고리즘
    게임잼
    버블정렬
    삽입정렬
    경북게임잼
    게임개발
    자료구조
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
Meoyoung's Development Logs
[Effective C++] 03. C++가 은근슬쩍 만들어 호출해 버리는 함수들에 촉각을 세우자
상단으로

티스토리툴바