2. 생성자, 소멸자 및 대입 연산자

보물창고·2021년 8월 7일
0

이펙티브 c++

목록 보기
3/6

항목5. c++이 은근슬쩍 만들어 호출하는 함수에 촉각을 세우자.

  • 컴파일러는 복사생성자, 복사대입 연산자, 소멸자를 몰래 만들어 가지고 있다.

  • 컴파일러가 만들어내는 복사 생성자 사용하기

  • 컴파일러가 만들어내는 복사대입 생성자 사용하기

-> 복사생성자를 만들어 주지 않았지만, 컴파일러가 스스로 복사 생성자를 호출시켜서 a클래스의 멤버 변수를 b에대가 대입시키는 것을 확인할 수 있다.
-> 그런데 이 작업은 코드가 적법하고, 이치에 닿아야만 가능하다.

클래스내 참조 변수의 초기화

: 참조가 변수는 주소가 있는 변수로 초기화를 해야한다.
주소가 없는 Rvalue로 초기화를 진행한다면 이렇게 문제가 생기고, 참조자 변수에는 데이터가 들어가지 않는다.

  • 이와 더불어 다른 문제가 있다. Call by Value형태에서는 항상 복사가 진행되므로, 생성자의 string in_Name에서는 복사로 "wontae"를 받으므로 a클래스의 name변수에는 여전히 쓰레기 값이 들어가 있다.

-> 레퍼런스 변수의 초기화는 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를 지정하더라고 주소값은 변경되지 않는 것을 확인할 수 있따.

항목 6.컴파일러가 만들어내는 함수가 필요없으면 이들의 사용을 금하자.

  • 복사 생성자, 복사 대입연산자를 막기 위해서 private에 정의없이 선언해놓자.

외전 - 임시 객체와 const 레퍼런스


-> 당연히 안된다. 왜냐하면 인자는 RValue이고, 매개변수는 레퍼런스 타입이기 때문에 해결하는 방법은 2가지가 있다.
-> c++의 임시 객체와 const 레퍼런스 참고하세용!

복사 대입연산자 호출을 막자

: 보통 객체를 생성 후에는 복사 및 복사대입으로 동일한 객체를 복사하는 작업은 하지 않는다. 컴파일러가 자동적으로 복사, 복사 대입연산자를 호출하는 것을 막자

  • 막는 방법은 2가지가 있다.
    1) 클래스 내의 private에다가 복사생성자와 복사 대입연산자를 정의없이 선언만하자. (출처 : 이펙티브 c++)
    - 복사 대입 연산자 막기

    - 복사 생성자 막기

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

항목 7. 다형성을 가진 기본 클래스에서는 소멸자를 반드시 가상 소멸자로 선언하자.

: 가상함수가 하나라도 있는 추상클래스는 반드시 소멸자를 virtual로 선언해서 파생클래스의 소멸자가 호출되도록 하자.

  • 가상 소멸자가 없는 컨테이너 : string, unordered_map, list,set, vector

항목 8. 예외가 소멸자를 떠나지 못하도록 붙들어 놓자.

  • 예외처리를 처리하는 함수를 따로 만들어서 관리하자.
    : 소멸자에서 예외처리가 발생하면 프로그램이 종료되는 시점에서 끝나므로 ,
    문제가 해결되지 않고 끝내는 등 불완전 종료, 미정의 동작이 발생할 수 있다.
    다른 함수 내에서 예외처리를 진행해 프로그램 실행 중에 예외를 처리하도록 만들자.

항목 9. 객체 생성 및 소멸 과정 중에는 절대로 가상 함수를 호출하지 말자.

: 부모쪽 생성자에 부모 클래스의 가상함수가 호출될 경우, 파생클래스의 오버라이딩 함수가 호출되지 않는다.
왜냐하면 파생클래스에 대한 초기화가 진행되지도 않았으므로, 파생클래스의 오버라이딩 함수 접근이 불가능하므로, 당연한 것이다.
-> 생성자와 소멸자에서 가상함수 호출을 금지하자.

항목 10. 대입 연산자는 *this의 참조자를 반환하게 하자.

  • =+ , =* 포함.

  • 관례라고는 하지만, 다른 이유가 있다.
    참조형식의 레퍼런스가 아닐 경우에는 대입 이후에 복사 생성자가 호출되기 때문이다.
    왜냐하면 클래스를 리턴할 경우에 임시 객체가 생성되고, 대입 후에 임시 객체에 대한 소멸자가 호출된다. 임시 객체 생성을 방지하기 위한 것이다.

  • 확인 앞 줄에서 임시 객체에 대한 소멸자가 호출되는 것을 확인할 수 있다.

  • 레퍼런스 타입으로 반환 할 경우에는 임시객체 생성이 되지 않는다.

항목 11. operaor= 에서는 자기 대입에 대한 처리가 빠지지 않도록 하자.

: 복사 함수 내에서 동적할당 해제 후 새롭게 할당할때 문제가 발생할 수 있다.


-> 자기 자신의 메모리를 해제할 후, 해제된 값으로 재할당을 시도하려고 해서 문제가 발생한 것이다.

  • 자기 대입 방지하는 코드

항목 12. 객체의 모든 부분을 빠짐없이 복사하자

: 상속을 한 파생클래스에서 복사 함수의 경우, 파생클래스에서의 멤버값들은 복사가 이루어지지만, 부모 클래스에서의 멤버 변수 복사가 이루어지지는 않는다.

예시

  • 부모 클래스

  • 파생클래스

  • 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();


}
profile
🔥🔥🔥

0개의 댓글