1. 변환 생성자(Conversion Constructor)
변환 생성자
는 매개변수가 1개인 생성자- 매개변수가 1개인 생성자(변환 생성자)는 묵시적(implicit) 형변환이 가능하여 의도하지 않게 호출되어 문제가 발생할 수 있음
- 아래의 예제코드를 보면 main()함수에서
TestFunc(5);
함수를 호출할 때 클래스 타입이 아닌 int 타입으로 형변환 발생 - 매개변수 형식이 맞지 않기 때문에 에러가 발생해야 정상이지만 아래의 코드에서는 문제 없이 실행 됨
#include <iostream>
using namespace std;
class CTestData
{
private:
int m_nData = 0;
public:
// 매개변수가 하나뿐인 생성자는 형변환이 가능하다. 변환 생성자라고도 함(Conversion Constructor)
CTestData(int nParam) : m_nData(nParam)
{
cout << "CTestData(int)" << endl;
}
CTestData(const CTestData &rhs) : m_nData(rhs.m_nData)
{
cout << "CTestData(const CTestData &)" << endl;
}
int GetData() const
{
return m_nData;
}
void SetData(int nParam)
{
m_nData = nParam;
}
};
void TestFunc(CTestData param)
{
cout << "TestFunc(): " << param.GetData() << endl;
}
int main()
{
cout << "***Begin***" << endl;
TestFunc(5);
cout << "***End***" << endl;
return 0;
}
- 앞서 언급한 예제코드에서 소멸자를 추가하고, TestFunc() 함수의 매개변수를 CTestData param ->
const CTestData ¶m
으로 변경하여 실행결과 확인
class CTestData
{
// 앞의 예제 중복코드 생략
// 소멸자 추가
~CTestData()
{
cout << "~CTestData()" << endl;
}
};
// 매개변수가 클래스 참조형으로 정의, 묵시적 형변환 발생 --> 변환 생성자 호출
void TestFunc(const CTestData ¶m)
{
cout << "TestFunc(): " << param.GetData() << endl;
}
// 실행결과
// ***Begin***
// CTestData(int)
// TestFunc(): 5
// ~CTestData()
// ***End***
- 실행결과를 보면
CTestData(int)
변환 생성자가 호출된 것을 알 수 있음 - 하지만, main()함수에서는 객체를 생성하는 코드가 없음
변환 생성자가 호출되었다는 것은 객체가 생성되었다는 것
을 의미- 컴파일러에 의해
임시객체
가 생성되고 이에 대한 참조가 TestFunc() 함수로 전달 - 임시객체는 함수의 종료와 함께 소멸자를 호출하여 소멸
TestFunc(5);
코드는 실제로TestFunc(CTestData(5));
와 같이 동작하게 됨- 변환 생성자에 의해 묵시적 형변환이 발생하는 것을 막기 위해
explicit
키워드 선언- 임시객체의 생성을 방지
- 사용자 모르게 변환 생성자 호출에 의한 형변환이 일어나는 것을 방지
class CTestData
{
// 중복코드 생략
public:
// explicit 키워드를 통해 묵시적 형변환이 일어나지 않도록 함
explicit CTestData(int nParam) : m_nData(nParam)
{
cout << "CTestData(int)" << endl;
}
// 중복코드 생략
};
int main()
{
TestFunc(5); // 컴파일 에러 발생!
return 0;
}
2. 임시 객체
- 함수의 리턴형이 클래스인 경우 생성
- 순간적으로 생성되고, 소멸
- 다음 예제코드의
b = TestFunc(10);
부분에서 임시객체가 생성되고, 대입연산 후 소멸 CTestData &ref = TestFunc(10);
과 같이 참조자를 이용하여 임시객체를 참조할 수 있음(이 경우 임시객체는 main 함수 종료 시 소멸)
#include <iostream>
using namespace std;
class CTestData
{
private:
int m_nData = 0;
char *m_pszName = nullptr;
public:
CTestData(int nParam, char *pszName): m_nData(nParam), m_pszName(pszName)
{
cout << "CTestData(int)" << m_pszName << endl;
}
~CTestData(){
cout << "~CTestData(int)" << m_pszName << endl;
}
CTestData(const CTestData &rhs) : m_nData(rhs.m_nData), m_pszName(rhs.m_pszName)
{
cout << "CTestData(const CTestData &): " << m_pszName << endl;
}
CTestData& operator=(const CTestData &rhs)
{
cout << "operator=" << endl;
// 값은 변경, 이름 값은 그대로 둠
m_nData = rhs.m_nData;
return *this;
}
int GetData() const
{
return m_nData;
}
void SetData(int nParam)
{
m_nData = nParam;
}
};
// CTestData 객체를 반환하는 함수 --> 임시객체가 생성되는 환경
CTestData TestFunc(int nParam)
{
// 객체 a는 지역 변수
// 따라서 함수가 반환되면 소멸 됨
CTestData a(nParam, "a");
return a;
}
int main()
{
CTestData b(5, "b");
cout << "***Before***" << endl;
// 함수 실행 후 결과값으로 임시객체가 생성되고, 대입 연산 후 즉시 소멸한다.
// 대입연산 후에 임시객체가 사라지는 시점이 중요!
b = TestFunc(10);
cout << "***After***" << endl;
cout << b.GetData() << endl;
return 0;
}
// 실행결과
// CTestData(int)b
// ***Before***
// CTestData(int)a
// CTestData(const CTestData &): a <-- 임시객체 생성
// ~CTestData(): a <-- 임시객체 소멸
// operator=
// ~CTestData(int)a
// ***After***
// 10
// ~CTestData(int)b
3. r-value 참조
- 대입 연산자의 오른쪽 항을
rvalue
라고 함 - r-value 참조자는
&
가 두 번 붙음(&&rRef
)
int main()
{
// r-value 참조자 선언
int &&data = 3 + 4;
cout << data << endl;
return 0;
}
- 연산에 의해 생성되는
r-value
도 임시객체 - r-value 참조자는 다음과 같은 경우에 사용
- 아래 예시코드에서
int &&rParam
을int &
로 사용할 경우'int'에서 'int &'(으)로 변환할 수 없습니다.
컴파일 에러 발생
// r-value 참조자형으로 매개변수를 전달 받음
void TestFunc(int &&rParam)
{
cout << "TestFunc(int &&)" << endl;
}
int main()
{
// r-value로 인자를 전달
TestFunc(3 + 4);
return 0;
}
4. 이동 시맨틱(Move Semantics)
- C++11에서 추가된 개념으로 이동 생성자, 이동 대입 연산자로 구현
- 임시객체를 다루기 위한 기능
- 복사 생성자와 대입 연산자에 r-value 참조를 조합하여 새로운 생성자를 정의
임시객체의 경우 곧 소멸되기 때문에 이동 생성자를 활용하여 깊은 복사가 아닌 얕은 복사를 통해 임시객체에 접근
#include <iostream>
using namespace std;
class CTestData
{
private:
int m_nData = 0;
public:
CTestData() { cout << "CTestData()" << endl; }
~CTestData() { cout << "~CTestData()" << endl; }
CTestData(const CTestData &rhs)
: m_nData(rhs.m_nData)
{
cout << "CTestData(const CTestData &)" << endl;
}
//이동 생성자, r-value 참조자 활용
CTestData(const CTestData &&rhs) : m_nData(rhs.m_nData)
{
cout << "CTestData(const CTestData &&)" << endl;
}
CTestData& operator=(const CTestData &ref) = default;
int GetData() const
{
return m_nData;
}
void SetData(int nParam)
{
m_nData = nParam;
}
};
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);
cout << "*After****************" << endl;
CTestData c(b);
return 0;
}
본 포스팅 내용은
이것이 C++이다
를 학습한 내용을 바탕으로 요약한 글입니다. 코드에 문제가 있을 수 있으며 다소 부정확한 내용이 있을 수 있으니 참고하시기 바랍니다. 잘못된 내용이 있다면 덧글로 남겨주시면 내용을 반영하여 수정하도록 하겠습니다. 감사합니다.
[Reference]
[1] 이것이 C++이다, p.202-226