[이것이 C++이다] Part 02. 객체지향 프로그래밍 (2)

Chapter 04. 복사 생성자와 임시 객체

  • C++ 에서 메모리 관리 기법을 익힙니다.
  1. 복사 생성자
  2. Shallow Copy vs Deep Copy
  3. 임시 객체
  4. 이동 시맨틱

4.1 복사 생성자

  • 복사 생성자는 객체의 복사본을 생성할 때 자동으로 호출됩니다.
  • 클래스를 작성할 때 복사 생성자를 생략하면 디폴트 생성자처럼 컴파일러가 알아서 만들어 줍니다.
  • 하지만 복사 생성자를 작성하지 않을 경우, 성능상 문제가 될 수도 있습니다.
  • 복사 생성자 문법
    CMyData(const CMyData& rhs);
    

4.2.1 복사 생성자 호출 시점

  • 명시적으로 객체의 복사본을 생성할 경우

    CMyData a;
    CMyData b(a); 
    
  • 클래스가 함수의 매개 변수로 사용되는 경우

    CMyData a;
    TestFunc(a);
    
    • 함수의 매개변수 형식이 클래스 형식이라면 무조건 상수형 참조로 선언 해야 합니다.
      void TestFunc(const CMyData& param);
      
  • 클래스가 함수의 반환 형식으로 사용되는 경우 (이름 없는 임시 객체와 연관이 깊다.)

    CMyData a;
    a = TestFunc();
    

4.1.2 깊은 복사와 얕은 복사

  • 깊은 복사 : 복사에 의해 실제로 두 개의 값이 생성되는 것.
  • 얕은 복사 : 하나의 대상에 대해 접근 포인터만 늘어난 것.
  • 얕은 복사의 문제점 ?

    int main() {
      int *pA, *pB;
      pA = new int;
      *pA = 10;
    
      pB = new int;
      pB = pA;  // shallow copy
    
      delete pA;
      delete pB;  // 이미 해제된 메모리를 한 번 더 해제하려고 한다.
      return 0;
    }
    
  • 어떻게 하면 될까 ?

    *pB = *pA;  // 두 포인터가 각각 다른 대상을 가리키게 된다.
    
  • 디폴트 복사 생성자의 문제점
    • 컴파일러가 자동으로 생성해 주는 디폴트 복사 생성자는 얕은 복사를 수행한다.
  • 올바른 복사 생성자 예제

    // 복사 생성자
    CMyData(const CMyData& rhs) {
      // 메모리를 할당한다.
      m_pnData = new int;
    
      // 포인터가 가리키는 위치에 값을 복사한다.
      *m_pnData = *rhs.m_pnData;
    }
    
    // 소멸자
    ~CMyData() {
      // 객체가 소멸하면 동적 할당한 메모리를 해제한다.
      delete m_pnData;
    }
    
    // 포인터 멤버변수
    private:
      int *m_pnData = nullptr;
    

4.1.3 대입 연산자

  • 단순 대입 (a = b;)을 시도하면 기본적으로 얕은 복사가 수행된다.
  • 올바른 대입 연산자 예제

    // 대입 연산자 오버로딩
    CMyData& operator=(const CMyData& rhs) {
      *m_pnData = *rhs.m_pnData;
      return *this;
    }
    
    private:
      int *m_pnData = nullptr;
    

4.2 묵시적 변환

4.2.1 변환 생성자

  • 변환 생성자의 함정
    • 불필요한 임시 객체를 만들어 내 프로그램의 효율을 갉아먹는다.
    class CTestData {
      public:
        // 매개변수가 하나 뿐인 생성자는 형 번환이 가능하다.
        CTestData(int nParam) : m_nData (nParam) { }
      private:
        int m_nData;
    };
    
    void TestFunc(CTestData param) { }
    int main() {
      // int 자료형에서 CTestData 형식으로 묵시적 변환
      TestFunc(5);
      return 0;
    }
    
  • explicit 예약어를 변환 생성자 앞에 붙여서 묵시적 변환을 막을 수 있습니다.

4.2.2 허용되는 변환

4.3 임시 객체와 이동 시멘틱

  • 이름 없는 임시 객체는 함수의 반환 형식이 클래스인 경우 발생합니다.

4.3.1 이름 없는 임시 객체

4.3.2 r-value 참조

4.3.3 이동 시맨틱

Chapter 05. 연산자 다중 정의

  • 연산자 함수에 대해 이해합니다. 연산자 함수는 오버로딩이 가능하여 기존에 사용하던 연산자보다 더 확장성 있는 코드를 생산할 수 있습니다.
  1. 연산자 함수
  2. 연산자 다중 정의 (오버 로딩)

5.1 연산자 함수란?

5.2 산술 연산자

5.3 대입 연산자

5.4 배열 연산자

5.5 관계 연산자

5.6 단항 증감 연산자