컴파일러는 복사생성자, 복사대입 연산자, 소멸자를 몰래 만들어 가지고 있다.
컴파일러가 만들어내는 복사 생성자 사용하기
컴파일러가 만들어내는 복사대입 생성자 사용하기
-> 복사생성자를 만들어 주지 않았지만, 컴파일러가 스스로 복사 생성자를 호출시켜서 a클래스의 멤버 변수를 b에대가 대입시키는 것을 확인할 수 있다.
-> 그런데 이 작업은 코드가 적법하고, 이치에 닿아야만 가능하다.
: 참조가 변수는 주소가 있는 변수로 초기화를 해야한다.
주소가 없는 Rvalue로 초기화를 진행한다면 이렇게 문제가 생기고, 참조자 변수에는 데이터가 들어가지 않는다.
-> 레퍼런스 변수의 초기화는 Call by 레퍼런스 타입으로 받아야 한다.
- 복사 대입연산자의 경우는, 불가능하다. 왜냐하면 레퍼런스는 생성과 동시에 초기화가 이루어져야 하고, 초기화 후에 다른 객체를 참조할수가 없기 때문이다!
-> 컴파일러가 알아서 컴파일 오류를 발생시키는 것을 확인할 수 있따.
유저가 클래스내에 참조자 변수를 작성할 경우, 따로 복사 대입 연산자를 정의해야 한다.
// + 상수멤버도 이와 유사하다.
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
class Object
{
public :
Object(string& in_Name, int in_Age)
: name{ in_Name }, age{in_Age}
{
cout << "Object constructor" << endl;
}
void speak() const
{
cout << name << endl;
cout << age << endl;
}
private :
string& name;
int age;
};
int main(void)
{
string wontae{ "wontae" };
Object a(wontae, 4);
string zzokki{ "zzokki" };
Object b(zzokki,5);
b = a;
a.speak();
b.speak();
}
-> 다른 Lvalue를 지정하더라고 주소값은 변경되지 않는 것을 확인할 수 있따.
-> 당연히 안된다. 왜냐하면 인자는 RValue이고, 매개변수는 레퍼런스 타입이기 때문에 해결하는 방법은 2가지가 있다.
-> c++의 임시 객체와 const 레퍼런스 참고하세용!
: 보통 객체를 생성 후에는 복사 및 복사대입으로 동일한 객체를 복사하는 작업은 하지 않는다. 컴파일러가 자동적으로 복사, 복사 대입연산자를 호출하는 것을 막자
2) public 에서 복사 생성자와 복사 대입 선언 후 옆에 delete 키워드를 작성하자. (출처 : 코드없는 프로그래밍 - 복사를 피하기 위해서 이렇게 작성하자.)
https://velog.io/@kwt0124/copy-assignment-move-assignment-%EB%8C%80%EC%9E%85#%EB%B3%B5%EC%82%AC%EB%A5%BC-%ED%94%BC%ED%95%98%EA%B8%B0-%EC%9C%84%ED%95%B4%EC%84%9C-%EC%9D%B4%EB%A0%87%EA%B2%8C-%EC%9E%91%EC%84%B1%ED%95%A8
: 가상함수가 하나라도 있는 추상클래스는 반드시 소멸자를 virtual로 선언해서 파생클래스의 소멸자가 호출되도록 하자.
: 부모쪽 생성자에 부모 클래스의 가상함수가 호출될 경우, 파생클래스의 오버라이딩 함수가 호출되지 않는다.
왜냐하면 파생클래스에 대한 초기화가 진행되지도 않았으므로, 파생클래스의 오버라이딩 함수 접근이 불가능하므로, 당연한 것이다.
-> 생성자와 소멸자에서 가상함수 호출을 금지하자.
=+ , =* 포함.
관례라고는 하지만, 다른 이유가 있다.
참조형식의 레퍼런스가 아닐 경우에는 대입 이후에 복사 생성자가 호출되기 때문이다.
왜냐하면 클래스를 리턴할 경우에 임시 객체가 생성되고, 대입 후에 임시 객체에 대한 소멸자가 호출된다. 임시 객체 생성을 방지하기 위한 것이다.
확인 앞 줄에서 임시 객체에 대한 소멸자가 호출되는 것을 확인할 수 있다.
: 복사 함수 내에서 동적할당 해제 후 새롭게 할당할때 문제가 발생할 수 있다.
-> 자기 자신의 메모리를 해제할 후, 해제된 값으로 재할당을 시도하려고 해서 문제가 발생한 것이다.
: 상속을 한 파생클래스에서 복사 함수의 경우, 파생클래스에서의 멤버값들은 복사가 이루어지지만, 부모 클래스에서의 멤버 변수 복사가 이루어지지는 않는다.
부모 클래스
파생클래스
main 함수
-> 결과 : 복사대입연산을 했지만, 부모의 값은 변경되지 않았따.
파생클래스에서 복사 대입연산을 진행하고자 할때는 파생클래스에서만 복사가 이루어지므로, 부모클래스에 대한 복사에 대한 작업을 해야한다.
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
class Parent
{
public :
Parent() {};
Parent(const int& in_a)
: a(in_a)
{
cout << "parent 생성자" << endl;
}
Parent(const Parent& other)
{
cout << "parent - copy constructor" << endl;
}
protected:
void Print()
{
cout << "부모의 값은 " << a << endl;
}
private :
int a;
};
class Object : public Parent
{
public :
Object(const string& in_Name, const int& in_Age , const int &parent_a)
: name{ in_Name }, age{in_Age}, Parent(parent_a)
{
cout << "Object constructor" << endl;
}
Object(const Object& other)
{
cout << "copy constructor" << endl;
}
Object& operator=(const Object& other)
{
Parent::operator=(other);
cout << "복사 대입 연산자 호출" << endl;
name = other.name;
age = other.age;
return *this;
}
~Object()
{
cout << "destructor" << endl;
}
void Print()
{
Parent::Print();
cout << "이름은 " << name << "나이는 " << age << endl;
}
private :
string name;
int age;
};
int main(void)
{
Object a("zzokki", 5, 5);
Object b("wontae", 6, 6);
cout << "a의 멤버 값들은 ";
a.Print();
cout << "b의 멤버 값들은 ";
b.Print();
cout << "복사 대입 연산자 이후 " << endl;
a = b;
cout << "a의 멤버 값들은 ";
a.Print();
cout << "b의 멤버 값들은 ";
b.Print();
}
-> 부모쪽의 복사 생성자를 호출할 필요가 있다!
변경된 복사생성자 부분
최종 소스코드
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
class Parent
{
public :
Parent() {};
Parent(int in_a)
: a(in_a)
{
cout << "parent 생성자" << endl;
}
Parent(const Parent& other)
: a(other.a)
{
cout << "parent - copy constructor" << endl;
}
protected:
void Print()
{
cout << "부모의 값은 " << a << endl;
}
private :
int a;
};
class Object : public Parent
{
public :
Object(const string& in_Name, const int& in_Age , const int &parent_a)
: name{ in_Name }, age{in_Age}, Parent(parent_a)
{
cout << "Object constructor" << endl;
}
Object(const Object& other)
: Parent(other)
{
cout << "복사 생성자 호출" << endl;
name = other.name;
age = other.age;
}
Object& operator=(const Object& other)
{
Parent::operator=(other);
cout << "복사 대입 연산자 호출" << endl;
name = other.name;
age = other.age;
return *this;
}
~Object()
{
cout << "destructor" << endl;
}
void Print()
{
Parent::Print();
cout << "이름은 " << name << "나이는 " << age << endl;
}
private :
string name;
int age;
};
int main(void)
{
Object a("zzokki", 5, 5);
//복사 생성자를 호출하자.
Object b(a);
cout << "a의 멤버 값들은 ";
a.Print();
cout << "b의 멤버 값들은 ";
b.Print();
}