IT공부/이것이 C++이다

객체지향 프로그래밍_2

doublehyun 2022. 3. 12. 03:31

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

(난이도 매우 높으니 여러번 복습할 것)

  • 복사 생성자
    • 복사 생성자란? : 객체의 복사본을 생성할 때 호출되는 '생성자'
      • 생략하면 디폴트 생성자 처럼 컴파일러가 알아서 만들어 넣어줌
      • 클래스 내부에서 메모리를 동적할당 및 헤제, 멤버포인터 변수로 관리 한다면 문제가 생긴다
        • ex) 클래스 내부에 new 로 변수 사용
      • 사용법 [ 클래스 이름( const 클래스 이름 &rhs); ]
        • 이때 const는 생략 가능하지만, 원본 객체를 손상시키지 않기 위해 const를 꼭 선언해주자! 
    • 복사 생성자 정리 ( 모든 상황 )
  1. 복사 생성자 (깊은 복사)
    1. 명시적 객체의 복사본 생성
    2. 함수의 형태로 호출
      1. 클래스가 함수의 매개변수로 사용 (무조건 참조 형태로 선언하기!)
      2. 클래스가 함수의 반환형태로 사용 (이름 없는 객체 생성됨!)
  2. 이동 시멘틱 (얕은 복사)
    1. 이동생성자
    2. 이동 대입연산자
  • 복사 생성자(게속)
    • 클래스가 함수의 매개변수로 사용되는 경우
      • 문제 : 의미없이 class 객체가 2번 사용됨
        1. 객체 선언시 1개 ( 함수의 인수로 넣기 위해 선언 )
        2. 함수의 매개변수로 클래스 객체를 받을때 다시 생성된 1개 ( 함수 내부에서 사용하기 위해 선언 )
      • Solution.1 : 복사 생성자 삭제(delete)
        • CTestData(const CTestData &rhs) = delete; 선언하기
        • 복사 생성이 아예 불가능하게 막아버림
      • Solution.2 : 참조자 사용 (&)
        • 매개변수로 클래스 객체를 받는 함수에서 매개변수로 클래스 객체의 참조자를 받는 함수로 변환시키기
        • ex) void TestFunc(CTest param) -> void TestFunc(const CTest &param) // 객체 생성은 1번만 됨
          • 이때도 const선언은 생략 가능하지만, 원본 객체를 손상시키지 않기 위해 const를 꼭 선언해주자! 
    • 깊은 복사와 얕은 복사
    • 얕은 복사는 메모리 관련 문제가 생긴다(둘다 delet 사용시 하나에 2번사용, 하나는 남아있는 상태가 되기 때문)
깊은 복사 얕은 복사
값이 2개, 각각 포인터 1개씩 값이 1개, 하나에 포인터 2개
int *pA, *pB;
pA = new int; / pB = new int;
*pA = 10;
*pB = *pA; pB = pA;
  • 복사 생성자(게속)
    • 대입 연산자
      • 단순 대입 연산자(=) 가 구조체나 클래스에도 기본적으로 적용됨
      • 하지만 단순 대입 연산자는 '얕은 복사' 가 수행된다!
      • 따라서 이때 연산자 다중 정의를 사용하여 문제를 해결해 주면 된다(나중에 배울 것)
        • ex) CMyData& operator=(const CMyData &rhs) { *m_pnData = *rhs.m_pnData; return *this; }
        • a = b (a.operator=(b) 와 같은 형식으로 호출 가능)
  • 묵시적 변환
    • 묵시적 변환이란?
      • 직접적인 언급은 없었으나 당연하게 변환 하는 것
    • 변환 생성자
      • 매개변수가 하나인 생성자를 '변환 생성자' 라고 한다!
      • ex) CTestData(int nParam) { ~ } // 변환 생성자
        • void TestFunc(CTestData param) { } / TestFunc(5); // 변환생성자 사용됨 
        • 원래 클래스를 맴버변수로 받지만, int 형으로 받음
      • 이는 실제로 임시 객체가 생성되어 전달된 것 즉 TestFunc(CTestData(5)) 와 동일한 의미를 가진다
      • 묵시적 변환 생성자는 편의성은 좋아지지만 효율성(성능)은 낮아진다
      • 이때 변환생성자 앞에 explicit 예약어를 사용하면 묵시적 변환이 불가능해진다(임시 객체 생성 X)
        • explicit CTestData(int nParam) { ~ }
    • 허용되는 변환
      • 변환 생성자가 있으면 int 자료형 -> CTestData 자료형 은 가능했다
      • 그러나 CTestData 자료형 -> int 자료형 은 불가능하다 이때 형변환 연산자를 사용하면 가능해진다
        1. operator int(void) { return m_nData; } / a 
        2. int(a) // 무조건 강제 변환 시킴 
        3. static_cast<int>(a) // 예외 상황을 제외하고는 변환 시켜줌
      • 웬만하면 static_cast<자료형>(이름) 으로 명시적 형변환을 사용하자!
    • 임시 객체와 이동 시멘틱
      • 함수의 반환 형식이 클래스인 경우
        • 함수의 반환형식이 클래스인 함수를 실행시키면 _tmain(메인함수)에 이름없는 객체가 생성, 소멸 된다
        • 함수를 반환(return) 하면서 생성, 대입 연산이 끝나면서 소멸 한다
          • 이때 '무조건' 이름없는 객체가 생성, 소멸 한다
        • '이름없는 임시 객체의 원본 = 임시 객체의 복사 생성이 끝난 후'
        • 이때 소멸 전에 연산이 가능하다 ex)TestFunc(10).GetData()
        • 이름없는 객체에 별칭을 부여해서 소멸 시점을 달라지게 할 수 있다
          • CTestData &rData = TestFunc(10) // 클래스 참조자 변수로 별칭을 부여! (소멸 시기를 인식하자)
      • r-value 참조
        • 3에4를 대입할 수 없듯 변수가 아닌 대상에 참조를 선언하는것은 허용되지 않는다
        • 그러나 C++11 표준 등장후 r-value 에 관한 참조자가 새로 제공됬다 ( int &&) 형식
        • 여기서 r-value : 연산자 따라 생성된 임시 객체!
        • 아래 표를 보고 어떤 상황에 무엇이 필요할지 선택해서 사용할 것
매개변수 형식 실인수 예 비고
TestFunc(int) int x=3; TestFunc(x); / TestFunc(3) / 
TestFunc(3+4);
 
TestFunc(int &) int x=3; TestFunc(x)  
TestFunc(int &&) int x=3; TestFunc(3); / TestFunc(3+4); / TestFunc(x + 4); 이때 TestFunc(x)는 불가능!
  • 묵시적 변환 게속
    • 이동 시멘틱
      • 이동 생성자 + 이동 대입 연산자
      • 복사생성자와 대입 연산자에 r-value 참조를 조합하여 새로운 생성자가 만들어 졌다
      • 이동 생성자 [ CTestData(CTestData &&rhs) { ~ } ] 복사생성자에 & 하나더
      • 만들어진 이유 : 얕은 복사를 하기 위해 (복사 생성자는 깊은 복사가 된다)

연습문제

1. 함수의 매개변수가 기본 형식이 아니라 클래스라면 매개변수 형식은 어떻게 정하는 것이 바람직한가? 그 이유는?

- 클래스의 참조자 형식, 의미없이 객체를 2번 할당(선언) 하지 않기 위해

2. 복사 생성자 및 단순 대입 연산자를 반드시 정의해야 하는 클래스는 어떤 클래스인가?

- 클래스 내부에서 동적할당(new, delete) 를 사용하고, 이를 참조자로 관리하는 클래스

3. 만약 다음과 같은 코드에서 컴파일 오류가 없었다면 CTestData 클래스는 잠재적인 문제를 가진것, 그것이 무엇인가?

void TestFunc(const CTestData &param) { ~ }

int _tmain(int argc, _TCHAR* argv[]) {

    TestFunc(5);

    return 0;

}

- 변환 생성자가 활용된다 -> 성능에 대한 문제

4. 이동 시멘틱이 등장한 가장 큰 원인은 무엇인가?

- 얕은 복사를 사용해서 메모리 공간을 효율적으로 사용하기 위해