![[Effective C++] 5. C++가 은근슬쩍 만들어 호출해 버리는 함수들에 촉각을 세우자](https://img1.daumcdn.net/thumb/R750x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdnmetf%2FbtrQTQlDFgx%2Fsa6Qf3YgJeh3TGkqpWZRn0%2Fimg.jpg)
Effective C++ 3rd Edition. Scott Meyers.
C++ 프로그래머의 필독서라고 불리는 Effective C++을 읽고 중요한 내용을 정리한 글 입니다.
Item5. C++가 은근슬쩍 만들어 호출해 버리는 함수들에 촉각을 세우자.
🔻생성자, 소멸자, 대입 연산자
모든 c++ 클래스에 한 개 이상 꼭 들어 있는 것이 생성자, 소멸자, 대입 연산자이다.
◽ 생성자 : 새로운 객체를 메모리에 만드는 데 필요한 과정을 제어하고 초기화를 맡는 함수.
◽ 소멸자 : 객체를 없앰과 동시에 그 객체가 메모리에서 적절히 사라질 수 있도록 하는 과정을 제어하는 함수.
◽ 대입 연산자 : 기존의 객체에 다른 객체의 값을 줄 때 사용하는 함수.
이들을 어떻게 하면 멋지게 모아둘 수 있을까?
🔻컴파일러가 자동으로 선언해 주는 멤버 함수
내가 클래스 안에 직접 선언하지 않으면, 컴파일러가 저절로 선언해 주도록 되어있는 함수가 있는데, 바로 복사 생성자, 복사 대입 연산자, 소멸자이다. 이때 컴파일로가 만드는 함수의 형태는 모두 기본형이다. 생성자도 선언되어 있지 않으면 컴파일러가 기본 생성자를 선언해 놓는다. 이들은 모두 public멤버이며 inline함수이다.
class Empty {};
우리가 위와 같이 선언했다면
class Empty
{
public:
Empty(){...} // 기본 생성자
Empty(const Empty &rhs){...} // 복사 생성자
~Empty(){...} // 소멸자
Empty& operator=(const Empty &rhs){...} // 복사 대입 연산자
};
이렇게 쓴 것과 동일하다는 말이다. 이 것들은 항상 만들어지는 것은 아니고, 컴파일러가 필요하다고 판단 할때만 만들어진다. 이때 소멸자는 상속한 기본 클래스의 소멸자가 가상 소멸자로 되어 있지 않으면 역시 비가상 소멸자로 만들어진다는 점 기억해두자. 복사 생성자와 복사 대입 연산자의 경우에는 원본 객체의 비정적 데이터를 사본 객체쪽으로 그냥 복사하는 것이 전부이다.
template <typename T>
class NamedObject
{
public:
NamedObject(const char *name, const T &value);
NamedObject(const std::string &name, const T &value);
// 복사 생성자 선언 안했어요~~
private : std::string nameValue;
T objectValue;
};
NamedObject<int> no1("Smallest Prime Number", 2);
NamedObject<int> no2(no1); // 복사 생성자 호출
이 예제를 보면, 현재 클래스에 생성자가 선언되어 있으므로, 컴파일러는 기본 생성자를 만들어내지 않을 것이다. 반면 복사 생성자나 복사 대입 연산자는 NameObject에 선언되어 있지 않아서 컴파일러에 의해 만들어지게 될 것이다. string은 string에 구현된 복사 생성자를 통해 복사가 이루어질것 이고, int는 기본제공 타입이므로 각 비트를 그대로 복사해올것이다. 복사 대입 연산자의 경우에도 비슷하게 동작할 것이다.
template<typename T>
class NamedObject
{
public:
NamedObject(std::string &name, const T &value);
...
//복시 대입 연산자 선언 안함
private : std::string &nameValue; // 레퍼런스 타입
const T objectValue; // 상수
};
다만 이러한 상황을 가정해보자.
std::string newDog("Persephone");
std::string oldDog("Satch");
NamedObject<int> p(newDog, 2);
NamedObject<int> s(oldDog, 36);
p = s;
이런 코드를 맞닥뜨렸을 때, p.nameValue는 s.nameValue가 참조하는 string을 가리켜야 맞을까? 아니면, p.nameValue가 참조하는 객체 자체가 바뀌는게 맞을까? 어느쪽을 정하더라고 문제가 생기기 때문에 컴파일너는 컴파일을 거부한다. 데이터 멤버가 상수 객체일때도 비슷하게 동작한다. 이렇때는 직접 복사 대입 연산자를 정의해 주어야한다.
또한 private로 선언한 기본 클래스로부터 파생된 클래스의 경우, 이 클래스는 암시적 복사 대입 연산자를 가질 수 없다.
💡 핵심!
🔸 컴파일러는 경우에 따라 클래스에 대해 기본 생성자, 복사 생성자, 복사 대입 연산자, 소멸자를 암시적으로 만들어
놓을 수 있다.
'C++ > Effective C++' 카테고리의 다른 글
[Effective C++] 6. 컴파일러가 만들어낸 함수가 필요없으면 확실히 이들의 사용을 금해버리자 (0) | 2022.11.10 |
---|---|
[Effective C++] 4. 객체를 사용하기 전에 반드시 그 객체를 초기화하자 (0) | 2022.11.09 |
[Effective C++] 3. 낌새만 보이면 const를 들이대 보자! (0) | 2022.11.09 |
[Effective C++] 2. #define을 쓰려거든 const, enum, inline을 떠올리자. (0) | 2022.11.08 |
[Effective C++] 1. C++을 언어들의 연합체로 바라보자. (2) | 2022.11.08 |
게임개발자를 꿈꾸는 대학생의 개발 공부 블로그
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!