복사생성자와 임시객체
→ 언제 호출되는 지에 대해서 알아야함! 성능, 오류와 관련되어 있기 때문에 잘 알아두어야 함!
객체의 복사본을 생성할 때 호출되는 생성자
“새롭게 생성되는 객체가 원본 객체와 내용이 같으면서도 완전한 독립성을 가진다”
클래스 이름(const 클래스이름 &rhs)
//const는 r-value reference 를 쓸 때 원칙처럼 사용! - 원본손상 예방
//EMC++인가 Google Style Guide에 나왔는데 기억 안남...
#include <iostream>
using namespace std;
class CMyData{
public:
CMyData(){
cout<<"CMydata()"<<endl;
}
CMyData(const CMyData &rhs){
this->m_nData = rhs.m_nData;
//생성되는 객체를 명시하고자 l-value에
//의도적으로 this->() 표현
cout<<"CMyDATA(const CMydata &)"<<endl;
}
int GetData(void) const {return m_nData;}
void SetData(int nParam) {m_nData = nParam;}
private:
int m_nData = 0;
};
int main(){
CMyData a; //선언&디폴트
a.SetData(10);
CMyData b(a); //복사 생성자 선언 및 정의
cout<<b.GetData()<<endl;
a.SetData(20);
cout<<a.GetData()<<endl;
cout<<b.GetData()<<endl;
return 0;
}
/* 출력결과
CMydata()
CMyDATA(const CMydata &)
10
20
10
*/
함수 호출과 복사 생성자
함수가 호출될 때 매개변수 param은 caller의 a를 복사해서 사용함
→ 두 개 만들고 복사까지 해야됨 → 성능ㅠㅠ
해결방법
복사 생성자를 삭제해버림 - 클래스 생성자에서 r-value reference를 통한 생성을 아예 없애버림
CTestData(const CTestDate &rhs) = delete;
참조자를 사용함
#include <iostream>
using namespace std;
class CTestData{
public:
CTestData(int nParam):m_nData(nParam){
cout<<"CTestData(int)"<<endl;
cout<<this->m_nData<<endl;
}
CTestData(const CTestData &rhs) : m_nData(rhs.m_nData){
cout<<"CTestData(CTestData &)"<<endl;
}
int GetData(void) const {return m_nData;}
void SetData(int nParam) {
m_nData = nParam;
cout<<this->m_nData<<endl;
}
private:
int m_nData = 0;
};
void TestFunc(const CTestData ¶m){
//a 객체를 복사생성하므로 param을 참조자로 불러옴.
//대신 원본 훼손을 막기 위해 const를 붙여주는 게 원칙.
// 그러면 SetData()가 안먹힘!!!
cout<<"TestFunc()"<<endl;
//param.SetData(20);
cout<<param.GetData()<<endl;
}
int main(){
cout<<"*****Begin*****"<<endl;
CTestData a(10);
TestFunc(a);
cout<<"a : "<<a.GetData()<<endl;
cout<<"*****End*****"<<endl;
return 0;
}
/* 출력결과
*****Begin*****
CTestData(int)
10
TestFunc()
10
a : 10
*****End*****
*/
깊은 복사와 얕은 복사
복사를 잘 사용하자…
#include <iostream>
using namespace std;
class CMyData{
public:
CMyData(int nParam){
m_pData = new int;
*m_pData = nParam;
}
CMyData(const CMyData &rhs){ //복사 생성자를 선언하여 deep copy를 만들어줌!
cout<<"CMyData(const CMydata &)"<<endl;
m_pData = new int;
*m_pData = *rhs.m_pData;
}
~CMyData(){
delete m_pData;
}
int GetData(){
if(m_pData != NULL){
return *m_pData;
}
return 0;
}
void SetData(int nParam) {*m_pData = nParam;}
private:
int *m_pData = nullptr;
};
int main(){
CMyData a(10);
CMyData b(a);
cout<<a.GetData()<<endl;
cout<<b.GetData()<<endl;
a.SetData(20);
cout<<a.GetData()<<endl;
cout<<b.GetData()<<endl;
}
/* 출력 결과
CMyData(const CMydata &)
10
10
20
10
*/
대입연산자
단순 대입(=)을 사용하면 shallow copy가 일어남.
따라서 연산자 다중정의(overload)를 통해서 deep copy를 해주어야 함.
operater= 를 통해서 ‘=’ 기본 연산자를 재정의할 수 있음!
#include <iostream>
using namespace std;
class CMyData{
public:
CMyData(int nParam){
m_pData = new int;
*m_pData = nParam;
}
CMyData(const CMyData &rhs){
cout<<"CMyData(const CMydata &)"<<endl;
m_pData = new int;
*m_pData = *rhs.m_pData;
}
~CMyData(){
delete m_pData;
}
CMyData& operator=(const CMyData &rhs){
*m_pData = *rhs.m_pData;
return *this;
}
int GetData(){
if(m_pData != NULL){
return *m_pData;
}
return 0;
}
void SetData(int nParam) {*m_pData = nParam;}
private:
int *m_pData = nullptr;
};
int main(){
CMyData a(10);
CMyData b(20);
CMyData c(30);
cout<<a.GetData()<<endl;
cout<<b.GetData()<<endl;
cout<<c.GetData()<<endl;
b=a;
c.operator=(a);
cout<<a.GetData()<<endl;
cout<<b.GetData()<<endl;
cout<<c.GetData()<<endl;
a.SetData(100);
cout<<a.GetData()<<endl;
cout<<b.GetData()<<endl;
cout<<c.GetData()<<endl;
}
/* 출력 결과
10
20
30
10
10
10
100
10
10
*/
변환 생성자(Conversion Constructor) :
생성자의 매개변수가 하나이고 해당 매개변수의 형을 출력하는 클래스의 경우, 매개변수 형식과 매우 높은 호환성을 가지므로 모호성이 발생함(int 1개를 생성자 매개변수로 하고, 멤버변수도 int 하나일 경우, int와 클래스가 헷갈릴 수 있음)
→ 불필요한 임시 객체를 만들어냄으로써 프로그램의 효율을 갉아먹음.
#include <iostream>
using namespace std;
class CTestData{
public:
CTestData(int nParam):m_nData(nParam){
cout<<"CTestData(int)"<<endl;
}
CTestData(const CTestData &rhs) : m_nData(rhs.m_nData){
cout<<"CTestData(const CTestData &)";
}
~CTestData(){
cout<<"~CTestData()"<<endl;
}
int GetData() const {
return m_nData;
}
void SetData(int nParam){
m_nData = nParam;
}
private :
int m_nData = 0;
};
void TestFunct(CTestData param){
cout<<" TestFunct() : "<<param.GetData()<<endl;
}
void TestFunct_r(const CTestData ¶m){
cout<<" TestFunct(&) : "<<param.GetData()<<endl;
}
int main(){
TestFunct(5);
TestFunct_r(5);
return 0;
}
/* 출력 결과
CTestData(int)
TestFunct() : 5
~CTestData()
CTestData(int)
TestFunct(&) : 5
~CTestData()
*/
void TestFunct() 함수 : CTestData 클래스가 int자료형에 대한 변환 생성자를 제공함 → param(5) 형태로 임시 객체가 생성됨
void TestFunct_r() 함수 : 컴파일러가 알아서 임시 객체를 생성한 후 임시객체에 대한 참조가 TestFunct_r()로 전달됨 → TestFunct_r(CTestData(5))
클래스를 매개변수로 사용하고자 한다면 참조형식으로 사용 + 변환 생성자 앞에 explicit 예약어 사용
→ 효율성을 중시, 사용자 코드에서 헷갈리지 않음
CTestData(int nParam) 앞에 ‘explicit’을 붙여주면 컴파일러가 임시객체를 생성하지 않음!
허용되는 변환
클래스가 변환 생성자를 지원하면 두 형식 사이에 호환성이 생김! → 그러나 반쪽짜리 변환…
클래스형은 int(혹은 다른 것)형으로 변환 불가
형변환 연산자로 변환 가능!!!
#include <iostream>
using namespace std;
class CTestData{
public:
explicit CTestData(int nParam):m_nData(nParam){
cout<<"CTestData(int)"<<endl;
}
operator int(void){
return m_nData;
}
int GetData() const {
return m_nData;
}
void SetData(int nParam){
m_nData = nParam;
}
private :
int m_nData = 0;
};
int main(){
CTestData a(10);
cout<<a.GetData()<<endl;
cout<<a<<endl; //10번줄 형변환 연산자
cout<<(int)a<<endl; //C++에서 추천하지X, C언어 스타일
cout<<static_cast<int>(a)<<endl; //C++스타일 형변환 연산자
return 0;
}
/* 출력 결과
CTestData(int)
10
10
10
10
*/
const_cast
, static_cast
, dynamic_cast
, reinterpret_cast
형변환 연산자가 존재함. → 7장!이름없는 임시 객체
클래스이름();
클래스이름(param);
클래스이름(const 클래스이름 &rhs)
클래스이름(const 클래스이름 &&rhs)
→ r-value reference 복사생성자와 대입연산자에 r-value reference를 조합해서 새로운 생성&대입을 한 것 곧 사라질 r-value에 대하여 복사생성하지 않고 shallow copy 한 다음 원본 포인터가 소멸하는 방식으로 성능을 높여버림!(스바…C++17 이라서 예제랑 결과가 안맞음…컴파일러 잘 골라서 돌리도록 하자…)
#include <iostream>
using namespace std;
class CTestData{
public :
CTestData() {
cout<<"CTestData()"<<endl;
}
~CTestData(){
cout<<"~CTestData() : "<<endl;
}
CTestData(CTestData &rhs)
: m_nData(rhs.m_nData)
{
cout<<"CTestData(CTestData &)"<<endl;
}
CTestData(CTestData &&rhs)
: m_nData(rhs.m_nData)
{
cout<<"CTestData(CTestData &&)"<<endl;
}
CTestData& operator=(const CTestData &) = default;
int GetData() const {return m_nData;}
void SetData(int nParam){m_nData = nParam;}
private :
int m_nData = 0;
};
CTestData TestFunc(int nParam){
cout<<"***TestFunc() Begin***"<<endl;
CTestData a;
a.SetData(nParam);
cout<<"***TestFunc() End***"<<endl;
return a;
}
int main(){
CTestData b;
cout<<"*****Before*****"<<endl;
b = TestFunc(20); **//TestFunc의 임시결과를 저장할 때 이동생성자 호출됨**
cout<<"*****After*****"<<endl;
CTestData c(b);
return 0;
}
+) 관련하여 읽어볼 자료
< Effective Modern Cpp > Item17. Special Memeber Function 자동 작성 조건을 숙지하라
-> 복사 생성자, 복사대입연산자, 이동 생성자, 이동대입연산자 에 대해서 나옴.