[기술 질문] #13_6가지 디폴트 멤버 메서드
클래스의 6가지 디폴트 멤버 메서드에 대해 알아보겠습니다.
Overview
- 개요
- 6가지 디폴트 메서드
- 대입 vs 생성, 복제 vs 이동
- 얕은/깊은 복사
#0. 개요
1. 클래스 제공 기본 메서드
- C++의 클래스는 사용자가 별도로 정의하지 않아도, 컴파일 시점에 컴파일러가 자동으로 생성하는 몇 가지 디폴트 메서드들이 존재합니다.
- 클래스가 제공하는 6가지 디폴트 혹은 암시적 메서드들은 생성자, 복사 생성자, 복사 대입 연산자, 이동 생성자, 이동 대입 연산자, 그리고 소멸자가 있습니다.
#1. 6가지 디폴트 메서드
1. 디폴트 생성자
#include <iostream>
using namespace std;
class MyClass {};
int main() {
MyClass* obj = new MyClass(); // 디폴트 생성자 호출
return 0;
}
Details
- C++의 클래스는 컴파일 시점에 컴파일러가 자동으로 디폴트 생성자를 생성합니다.
- 위 예제 코드에서 "MyClass" 클래스는 별도의 생성자를 정의하지 않았음에도 불구하고, 정상적으로 새로운 객체를 생성하고 있습니다.
2. 디폴트 소멸자
#include <iostream>
using namespace std;
class MyClass {
public:
// constructor
MyClass() {
cout << "Object of MyClass created" << endl;
}
// destructor (default)
~MyClass() {
cout << "Object of MyClass destroyed" << endl;
}
};
int main() {
{
MyClass obj; // creating an object of MyClass
}
return 0;
}
* 결과
Object of MyClass created
Object of MyClass destroyed
Details
- 위와 동일하게, C++의 클래스는 사용자가 별도로 정의하지 않아도, 기본적으로 소멸자 메서드를 제공합니다.
3. 디폴트 복사 생성자
#include <iostream>
using namespace std;
class MyClass {
public:
// constructor
MyClass(int value) {
data = value;
cout << "Object of MyClass created with value " << data << endl;
}
private:
int data;
};
int main() {
MyClass obj1(42); // creating an object of MyClass
{
MyClass obj2 = obj1; // copying obj1 to create obj2
}
return 0;
}
* 결과
Object of MyClass created with value 42
Object of MyClass created with value 42
Details
- C++의 클래스에서 기본적으로 제공하는 디폴트 복사 생성자는 이미 생성된 같은 유형의 객체를 복사해 새로운 객체를 생성합니다.
- 이때, 디폴트 복제 생성자의 복제 작업은 "얕은 복제"입니다.
4. 디폴트 복제 대입 연산자
#include <iostream>
class MyClass {
public:
MyClass(int i, double d, const std::string& str) : myInt(i), myDouble(d), myString(str) {}
void print() const {
std::cout << "myInt: " << myInt << ", myDouble: " << myDouble << ", myString: " << myString << std::endl;
}
private:
int myInt;
double myDouble;
std::string myString;
};
int main() {
// Create an object of MyClass
MyClass obj1(42, 3.14, "hello");
// Create a second object and assign obj1 to it using the default copy assignment operator
MyClass obj2 = obj1;
// Print both objects to confirm they are the same
std::cout << "obj1: ";
obj1.print();
std::cout << "obj2: ";
obj2.print();
return 0;
}
* 결과
obj1: myInt: 42, myDouble: 3.14, myString: hello
obj2: myInt: 42, myDouble: 3.14, myString: hello
Details
- C++의 클래스에서 제공하는 디폴트 복제 대입 연산자는 동일한 클래스 유형의 서로 다른 객체들을 서로에게 대입할 때 사용됩니다.
- 이때, 디폴트 복제 대입 연사자 함수는 "얕은 복제"를 수행합니다.
5. 디폴트 이동 생성자
#include <iostream>
class MyClass {
public:
int* data;
MyClass(int size) {
data = new int[size];
}
~MyClass() {
delete[] data;
}
// No move constructor defined
};
int main() {
MyClass a(10);
MyClass b = std::move(a);
std::cout << "a.data = " << a.data << std::endl;
std::cout << "b.data = " << b.data << std::endl;
return 0;
}
Details
- 정의 : C++의 디폴트 이동 생성자는 r-value(우항 값)에 해당하는 객체가 갖는 자원들에 대한 소유권을 새로 생성한 객체로 이전합니다. 이때, 소유권을 이전당한 객체는 유효하지 않은 상태로 전환됩니다.(*Null 값으로 설정되지 않고, 단지 유효하지 않은 상태로 전환됩니다)
- 특징 : 디폴트 이동 생성자는 "새로운 메모리 영역의 할당" 없이 단순히 기존의 메모리 영역에 대한 소유권을 잃고, 전달하는 동작을 수행합니다.
6. 디폴트 이동 대입 연산자
#include <iostream>
using namespace std;
class MyClass {
public:
MyClass(int size) : m_size(size), m_data(new int[size]) {}
~MyClass() { delete[] m_data; }
private:
int m_size;
int* m_data;
};
int main() {
MyClass a(10);
MyClass b(20);
// Use default move assignment operator to transfer ownership of resources from 'b' to 'a'
a = std::move(b);
return 0;
}
Details
- 정의 : 디폴트 이동 대입 연산자는 동일한 클래스 유형의 서로 다른 두 객체가 할당 받은 메모리 영역에 대한 소유권을 서로에게 이전합니다. 소유권을 이전 당한 객체는 유효하지 않은 상태로 전환되어, 더 이상 접근할 수 없습니다.
#2. 대입 vs 생성, 복제 vs 이동
1. 생성 vs 대입
- 복제 생성자는 이미 존재하는 동일한 클래스 유형의 객체를 통해 새로운 객체의 초기화를 수행합니다. 복제 생성자는 새로운 객체 생성 시 딱 한번 호출되겠죠.
- 복제 대입 연산자는 동일한 클래스 유형의 서로 다른 두 개체들이 서로에게 대입할 때 호출됩니다. 어떤 객체의 생명 주기 동안 지속적으로 호출될 가능성이 있겠죠.
2. 복제 vs 이동
- 동작 방식 : 디폴트 복사 생성자 혹은 디폴트 복사 대입 연산자는 '얕은 복제'를 수행하며, 만약, 원본 객체의 데이터 멤버가 포인터 변수일 경우, 그 피상적인 주소값만 복사되어 복제본의 데이터 멤버는 같은 메모리 영역을 참조합니다. 반면, 디폴트 이동 생성자 혹은 이동 대입 연산자는 '얕은 복제'를 수행하지만, 원본 객체의 데이터 멤버가 포인터 변수일 경우, 포인터 변수가 가리키던 메모리 영역에 대한 소유권을 복제된 객체에게 넘겨주어, 원본 객체는 유효하지 않은 상태로 전환되어, 더 이상 접근이 불가능한 상태가됩니다.
- 리스크 : 디폴트 복사 생성자 혹은 복사 대입 연산자의 경우 일반적으로 "얕은 복제"를 수행합니다. 이 과정에서, 원본 객체의 포인터 멤버의 피상적인 주솟값이 복사되어, 복제된 객체와 원본 객체가 동일한 메모리 영역을 참조합니다. 따라서, 허상 포인터(Dangling Pointer)의 위험이 존재합니다. 반면에, 디폴트 이동 생성자 혹은 이동 대입 연산자는 원본 객체의 포인터 멤버가 가리키는 메모리 영역의 소유권을 복제된 객체로 이전되어, 원본 객체는 더 이상 접근 불가능한 유효하지 않은 상태로 전환됩니다. 만약, 이후 원본 객체에 접근하거나, "자기 할당"에 대한 처리를 잘못할 경우 정의되지 않은 동작이 발생해 오류 발생의 원인이 될 수 있습니다.
- 성능 : 복제 작업과 달리 이동 작업은 새로운 메모리 영역을 할당받지 않기 때문에 비교적 빠르고 효율적입니다. 하지만, 이동 작업의 경우 원본 객체를 유효하지 않은 상태로 변경하기 때문에 특정 상황에만 활용할 수 있습니다. 더불어, 이동 작업은 동적 할당 받은 메모리 영역 같이 비교적 복잡한 자원들에 대한 정확한 소유권 이전을 위해 별도의 작업이 더 요구됩니다.
#3. 얕은/깊은 복사
1. 링크 참조
'언어 > 기술 질문' 카테고리의 다른 글
[기술 질문]#15_가상 함수 (0) | 2023.03.20 |
---|---|
[기술 질문]#14_템플릿, Template (0) | 2023.03.12 |
[기술 질문]#12_함수 포인터, Function Pointer (0) | 2023.02.22 |
[기술 질문]#11_허상 포인터(Dangling Pointer) (2) | 2023.02.18 |
[기술 질문]#10_실수형의 문자열 변환 (0) | 2023.02.18 |