2024. 3. 26. 01:11ㆍ프로그래밍/C++
변환 생성자가 묵시적으로 호출되는 것을 explicit 예약어로 막으려는 이유는 사용자 코드에서 보이지 않는 객체가 생성되고 소멸하는 것을 막기 위함입니다. 그런데 이보다 더 은밀한 임시 객체도 있습니다.
참고로 이런 일은 '함수의 반환 형식이 클래스인 경우'에 발생합니다.
이름 없는 임시 객체
존재하는 인스턴스지만 '식별자'가 부여되지 안흔 객체를 말합니다.
#include <iostream>
using namespace std;
class CTestData
{
public:
CTestData(int nParam, char *pszName) : m_nData(nParam), m_pszName(pszName)
{
cout <<"CTestData(int): "<<m_pszName<<endl;
}
~CTestData()
{
cout<<"~CTEstData(): "<<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;}
private:
int m_nData=0;
char *m_pszName=nullptr;
};
CTestData TestFunc(int nParam)
{
//CTestData 클래스 인스턴스인 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;
}
main()
|
| CTestData b(5, "b"); (1)
| -------------------------------
| | m_nData : 5 |
| | m_pszName : "b" |
| -------------------------------
|
| *****Before***** (2)
| ------------------------------
| | b=TestFunc(10); |
| ------------------------------
| | (4)
| |
| V
| TestFunc(10) (3)
| -------------------------------
| | CTestData a(10, "a"); |
| -------------------------------
|
| operator= (복사 대입 연산자 호출) (5)
| ---------------------------------
| | m_nData : 10 |
| | m_pszName : "a" |
| ---------------------------------
|
| ~CTEstData(): "a" (6)
|
| *****After***** (7)
| -------------------------------
| | b |
| -------------------------------
| m_nData : 10
| m_pszName : "a"
|
-----------------------------------
위 도식은 다음을 나타냅니다:
- main() 함수에서 **CTestData b(5, "b");**가 생성됩니다.
- "*****Before*****" 메시지가 출력됩니다.
- TestFunc(10) 함수가 호출되고, **CTestData a(10, "a");**가 생성됩니다.
- TestFunc(10) 함수가 반환된 후, 복사 대입 연산자가 호출되어 **a**의 데이터가 **b**로 복사됩니다.
- "*****After*****" 메시지가 출력됩니다.
- **a**가 소멸됩니다.
- **b**의 데이터가 출력됩니다.
r-value 참조
연산에 따라 생성된 임시 객체
이동 시맨틱(move sementics)
- 이동 생성자
- 이동 대입 연산자
임시 객체가 생기는 것은 막을 수가 없다. 그래서 임시 객체가 생성되더라도 부하가 최소화될 수 있도록 구조를 변경한다.
복사 생성자와 대입 연산자에 r-value 참조를 조합해서 새로운 생ㅅ어 및 대입의 경우를 만들어낸다.
#include <iostream>
using namespace std;
class CTestData
{
public:
CTestData() {cout<<"CTestData()"<<endl;}
~CTestData() {cout<<"~CTestData()"<<endl;}
CTestData(const CTestData &rhs) : m_nData(rhs.m_nData)
{
cout<<"CTestData(const 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);
cout<<"*After*****************"<<endl;
CTestData c(b);
return 0;
}
임시 객체는 함수 내부에서 생성되어 함수 외부에서는 직접적으로 접근할 수 없는 객체를 말합니다. TestFunc() 함수에서 반환되는 CTestData a;는 임시 객체입니다. 이 임시 객체는 b = TestFunc(20);에서 b에 할당될 때 이동 생성자를 통해 데이터의 소유권을 b로 이전합니다.
따라서 이 코드에서 이동 생성자는 임시 객체를 b에 할당하여 데이터의 소유권을 전달합니다.
main() |
CTestData b; (1)
main()
|
| CTestData b; (1)
| -------------
| | m_nData |
| -------------
|
| *Before***************** (2)
| ------------------------------
| | b = TestFunc(20); |
| ------------------------------
| | (4)
| |
| V
| **TestFunc(): Begin*** (3)
| ------------------------------
| | CTestData a; |
| | -------------- |
| | | m_nData | |
| | -------------- |
| | |
| | SetData(20); |
| ------------------------------
| | (5)
| |
| V
| **TestFunc(): End***** (6)
| ------------------------------
| | a (temporary) |
| | -------------- |
| | | m_nData | |
| | | 20 | |
| | -------------- |
| ------------------------------
| | (7)
| |
| V
| *After***************** (8)
| ------------------------------
| | b (moved) |
| | -------------- |
| | | m_nData | |
| | | 20 | |
| | -------------- |
| ------------------------------
| | (9)
| |
| V
| ------------------------------
| | c (copied) |
| | -------------- |
| | | m_nData | |
| | | 20 | |
| | -------------- |
| ------------------------------
|
------------------------------
위 도식은 다음을 나타냅니다:
- main() 함수에서 **CTestData b;**가 생성됩니다.
- "*Before*****************" 메시지가 출력됩니다.
- TestFunc(20) 함수가 호출되고, "**TestFunc(): Begin***" 메시지가 출력됩니다.
- **CTestData a;**가 생성됩니다.
- **a.SetData(20);**이 호출됩니다.
- "**TestFunc(): End*****" 메시지가 출력되고, a (임시 객체)가 소멸됩니다.
- a (임시 객체)가 **b**에 할당될 때 이동 생성자가 호출됩니다.
- "*After*****************" 메시지가 출력되고, **b**에는 이동된 데이터가 있습니다.
- **b**를 복사하여 **c**가 생성됩니다.
깊은 복사 (Deep Copy)
깊은 복사는 객체의 모든 멤버나 자원을 복사하는 것을 의미합니다. 이는 새로운 메모리 공간이 할당되고, 원본 객체의 자원이나 멤버들이 새로운 객체로 복사되는 것을 의미합니다.
도식:
markdownCopy code
원본 객체:
---------------
| Data: "Hello" |
---------------
깊은 복사:
---------------------------
| Copied Data: "Hello" |
---------------------------
샘플 코드:
cppCopy code
#include <iostream>#include <cstring> // For strcpyclass DeepCopyExample {
private:
char* data;
public:
DeepCopyExample(const char* str) {
data = new char[strlen(str) + 1];
strcpy(data, str);
}
// 복사 생성자 (깊은 복사 수행)
DeepCopyExample(const DeepCopyExample& other) {
data = new char[strlen(other.data) + 1];
strcpy(data, other.data);
}
~DeepCopyExample() {
delete[] data;
}
const char* GetData() const {
return data;
}
};
int main() {
DeepCopyExample original("Hello");
DeepCopyExample copied(original); // 깊은 복사 수행
std::cout << "Original data: " << original.GetData() << std::endl;
std::cout << "Copied data: " << copied.GetData() << std::endl;
return 0;
}
얕은 복사 (Shallow Copy)
얕은 복사는 단순히 객체의 주소나 포인터만을 복사하는 것을 의미합니다. 이 경우 원본과 복사본이 같은 메모리 자원을 가리키게 됩니다. 따라서 둘 중 하나가 수정될 경우 다른 하나도 영향을 받게 됩니다.
도식:
markdownCopy code
원본 객체:
---------------
| Data: "Hello" |
---------------
얕은 복사:
-------------------
| Copied Data: --|----\\
------------------- |
|
V
-------------------
| Shared Data: "Hello" |
-------------------
샘플 코드:
cppCopy code
#include <iostream>class ShallowCopyExample {
private:
char* data;
public:
ShallowCopyExample(const char* str) {
data = const_cast<char*>(str); // 얕은 복사: 주소 복사
}
// 복사 생성자 (얕은 복사 수행)
ShallowCopyExample(const ShallowCopyExample& other) : data(other.data) {}
const char* GetData() const {
return data;
}
};
int main() {
ShallowCopyExample original("Hello");
ShallowCopyExample copied(original); // 얕은 복사 수행
std::cout << "Original data: " << original.GetData() << std::endl;
std::cout << "Copied data: " << copied.GetData() << std::endl;
return 0;
}
이동 생성 (Move Construction)
이동 생성은 객체의 자원을 다른 객체로 이전하는 것입니다. 이는 데이터를 복사하는 대신, 소유권을 이전하여 효율적으로 작동하게 합니다.
도식:
markdownCopy code
원본 객체:
---------------
| Data: "Hello" |
---------------
이동 생성:
---------------------------
| Moved Data: "Hello" |
---------------------------
샘플 코드:
cppCopy code
#include <iostream>#include <utility> // For std::moveclass MoveConstructorExample {
private:
char* data;
public:
MoveConstructorExample(const char* str) {
data = new char[strlen(str) + 1];
strcpy(data, str);
}
// 이동 생성자 (원본 객체의 자원을 새 객체로 이전)
MoveConstructorExample(MoveConstructorExample&& other) : data(std::move(other.data)) {
other.data = nullptr; // 원본 객체를 초기화하여 무효화
}
~MoveConstructorExample() {
delete[] data;
}
const char* GetData() const {
return data;
}
};
int main() {
MoveConstructorExample original("Hello");
MoveConstructorExample moved(std::move(original)); // 이동 생성
std::cout << "Original data: " << original.GetData() << std::endl; // 이동 후에는 무효화됨
std::cout << "Moved data: " << moved.GetData() << std::endl;
return 0;
}
'프로그래밍 > C++' 카테고리의 다른 글
상속이란 (1) | 2024.03.27 |
---|---|
묵시적 변환 - 변환 생성자(Conversion Constructor) (0) | 2024.03.25 |
대입 vs 복사 (0) | 2024.03.22 |
Pass by value VS Pass by reference (0) | 2024.03.21 |
대입 연산자 (0) | 2024.03.21 |