IT공부/이것이 C++이다
객체지향 프로그래밍_2
doublehyun
2022. 3. 12. 03:31
Chapter.4 복사 생성자와 임시 객체
(난이도 매우 높으니 여러번 복습할 것)
- 복사 생성자
- 복사 생성자란? : 객체의 복사본을 생성할 때 호출되는 '생성자'
- 생략하면 디폴트 생성자 처럼 컴파일러가 알아서 만들어 넣어줌
- 클래스 내부에서 메모리를 동적할당 및 헤제, 멤버포인터 변수로 관리 한다면 문제가 생긴다
- ex) 클래스 내부에 new 로 변수 사용
- 사용법 [ 클래스 이름( const 클래스 이름 &rhs); ]
- 이때 const는 생략 가능하지만, 원본 객체를 손상시키지 않기 위해 const를 꼭 선언해주자!
- 복사 생성자 정리 ( 모든 상황 )
- 복사 생성자란? : 객체의 복사본을 생성할 때 호출되는 '생성자'
- 복사 생성자 (깊은 복사)
- 명시적 객체의 복사본 생성
- 함수의 형태로 호출
- 클래스가 함수의 매개변수로 사용 (무조건 참조 형태로 선언하기!)
- 클래스가 함수의 반환형태로 사용 (이름 없는 객체 생성됨!)
- 이동 시멘틱 (얕은 복사)
- 이동생성자
- 이동 대입연산자
- 복사 생성자(게속)
- 클래스가 함수의 매개변수로 사용되는 경우
- 문제 : 의미없이 class 객체가 2번 사용됨
- 객체 선언시 1개 ( 함수의 인수로 넣기 위해 선언 )
- 함수의 매개변수로 클래스 객체를 받을때 다시 생성된 1개 ( 함수 내부에서 사용하기 위해 선언 )
- Solution.1 : 복사 생성자 삭제(delete)
- CTestData(const CTestData &rhs) = delete; 선언하기
- 복사 생성이 아예 불가능하게 막아버림
- Solution.2 : 참조자 사용 (&)
- 매개변수로 클래스 객체를 받는 함수에서 매개변수로 클래스 객체의 참조자를 받는 함수로 변환시키기
- ex) void TestFunc(CTest param) -> void TestFunc(const CTest ¶m) // 객체 생성은 1번만 됨
- 이때도 const선언은 생략 가능하지만, 원본 객체를 손상시키지 않기 위해 const를 꼭 선언해주자!
- 문제 : 의미없이 class 객체가 2번 사용됨
- 깊은 복사와 얕은 복사
- 얕은 복사는 메모리 관련 문제가 생긴다(둘다 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 자료형 은 불가능하다 이때 형변환 연산자를 사용하면 가능해진다
- operator int(void) { return m_nData; } / a
- int(a) // 무조건 강제 변환 시킴
- 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 ¶m) { ~ }
int _tmain(int argc, _TCHAR* argv[]) {
TestFunc(5);
return 0;
}
- 변환 생성자가 활용된다 -> 성능에 대한 문제
4. 이동 시멘틱이 등장한 가장 큰 원인은 무엇인가?
- 얕은 복사를 사용해서 메모리 공간을 효율적으로 사용하기 위해