복사생성자 & 복사 대입 연산자
class Test {
private:
int m_i;
public:
// 기본 생성자(default constructor)
Test(){
this->m_i = 0;
cout << "call the default constructor" << endl;
}
// 복사 생성자(copy constructor)
Test(const Test &t) {
this->m_i = t.m_i;
cout << "call the copy constructor" << endl;
}
// 복사 대입 연산자(copy assignment operator)
Test& operator=(const Test &t) {
this->m_i = t.m_i;
cout << "call the copy assignment operator" << endl;
return *this;
}
};
Test t1; => 기본 생성자
Test t2(t1); => 복사 생성자
Test t3 = t1; => 복사 생성자
t1 = t2; => 복사 대입 연산자
복사 생성자 | 객체의 복사본을 생성할 때 호출되는 생성자 | 따로 정의를 해주지 않을 경우 컴파일러가 알아서 생성 (단 이렇게 따로 정의를 하지 않고 컴파일러가 알아서 생성해주는 디폴트 복사 생성자, 디폴트 복사 대입 연산자는 모두 얕은 복사를 합니다.) |
복사 대입 연산자 | 같은 타입의 객체를 이미 생성되어 있는 객체에 값을 복사할 때 사용 |
생성자 이니셜라이저 vs 멤버 이니셜라이저
// 생성자 이니셜라이저 : 객체 생성 후 대입 (초기화가 아니다)
class A
{
private:
string name;
int num;
public:
A(string _name, int _num)
{
name = _name;
num = _num;
}
};
// 멤버 이니셜라이저 : 객체 생성과 동시에 초기화
class B
{
private:
string name;
int num;
public:
B(string _name, int _num): name(_name), num(_num) {}
};
생성자(constructor)는 객체 생성 시 딱 한번 호출된다.
생성자는 클래스의 이름과 함수의 이름이 동일하고 반환형이 선언되어 있지 않으며, 실제로 반환하지 않는다.
A 클래스 {생성자 이니셜라이저} 는 객체를 생성하고 멤버변수를 대입하고 기본생성자 호출 -> 복사 대입연산자 호출
B 클래스 {멤버 이니셜라이저} 는 멤버변수 초기화 동시에 객체를 생성한다. 복사생성자 호출
멤버 이니셜라이저는 생성자가 호출되기 전에 멤버들을 초기화 시킨다.
멤버 이니셜라이저를 통한 초기화는 객체 생성과 동시에 이루어 지기 때문
기본 생성자 대신 멤버 이니셜라이저를 선호하는 이유
1. 초기화 : 객체가 항상 초기화 된다는 보장이 없다. 따라서 모든 객체를 사용하기전 항상 초기화 해주어야한다.
C++ 규칙에 의하면 어떤 객체이든 그 객체의 데이터 멤버는 생성자의 본문이 실행되기 전에 초기화되어야 한다고 명시되어 있다.
2. 기본 생성자 + 복사 대입 연산자{A클래스} 보다 복사 생성자{B클래스}를 한번만 호출하는 쪽이 대부분의 데이터 타입의 경우, 더 효율적이다.
기본 생성자 대신 멤버 이니셜라이저 초기화가 강제될 때
1. 상수 멤버 변수 초기화
2. 레퍼런스 멤버 변수 초기화
3. 멤버 객체 초기화
4. 상속 멤버 변수 초기화
1. 상수 멤버 변수 초기화 - const 멤버변수가 있을 때 const는 선언과 동시에 초기화 (변경할 수 없으므로) 객체를 생성하고 대입하는 생성자에서는 const 멤버변수를 초기화 할 수 없지만 멤버 이니셜라이저에서는 초기화가 가능하다.
class A {
private:
const int num;
public:
A(int _num) { num = _num; } // error : 객체가 이미 생성되었으므로 const인 num을 수정 불가능
};
class B {
private:
const int num;
public:
B(int _num) : num(_num) {}
};
2. 레퍼런스 멤버 변수 초기화 - 레퍼런스 멤버 변수(참조형) 또한 선언시에 바로 초기화 되어야한다.
(아래코드에서의 &는 주소(address)를 의미하지 않고 참조(reference)를 의미)
class A {
private:
int &ref_num;
public:
A(int _num) { ref_num = _num; } // error : 참조형 또한 선언과 동시에 초기화 되어야 함
};
class B {
private:
int &ref_num;
public:
B(int _num) : ref_num(_num) {}
};
3. 멤버 객체 초기화 - 클래스내에 객체 멤버가 있는경우 해당 객체의 생성자가 호출되어야 한다. 매개변수를 가진 생성자를 초기화 해야 할 때도 초기화리스트의 사용이 강제 된다.
class Inner{
private:
int x;
int y;
public:
Inner(int _x, int _y): x(_x), y(_y) {}
};
class Outter{
private:
Inner inner;
int z;
public:
Outter(int _x, int _y, int _z):inner{_x,_y}, z(_z) {}
};

4. 상속 멤버 변수 초기화 - 멤버 객체 초기화 이유랑 비슷하다.
class Parent{
private:
int x;
int y;
public:
Parent(int _x, int _y): x(_x), y(_y) {}
};
class Child : public Parent {
private:
int z;
public:
Child(int _x, int _y, int _z): Parent(_x, _y), z(_z) {}
};
(Parent 생성자 -> Child 생성자 -> Child 소멸자 -> Parent 소멸자 순으로 불리므로)
멤버 이니셜라이저 초기화 순서
class Outter{
private:
Inner2 inner2;
Inner1 inner1;
Inner3 inner3;
public:
Outter(int _x, int _y):
inner1(_x, _y),
inner2(_x, _y),
inner3(_x, _y)
{
cout << "Outter" << endl;
}
};
생성자에서 대입순서대로 이루어 지는것과 달리 멤버 이니셜라이저에서는 선언된 멤버변수 순서대로 초기화 된다.
'프로그래밍 > C++' 카테고리의 다른 글
전방 선언 (0) | 2023.05.21 |
---|---|
레퍼런스 (참조자) (0) | 2023.03.01 |
C++ 람다 예제 (0) | 2023.01.02 |
Thread (0) | 2022.06.15 |
new, new[] / delete, delete[] (0) | 2022.05.27 |