[Effective C++] 항목 12 : 객체의 모든 부분을 빠짐없이 복사하자

수민이슈·2023년 3월 19일
0

Effective C++

목록 보기
12/30
post-thumbnail

스콧 마이어스의 Effective C++을 읽고 개인 공부 목적으로 요약 작성한 글입니다!

💡 객체 복사 함수는 주어진 객체의 모든 데이터 멤버기본 클래스 부분을 빠뜨리지 말고 복사하자!
💡 클래스의 복사 함수 2개를 구현할 때, 한 쪽을 이용해서 다른 쪽을 구현하려는 시도는 하지 말자!
💡 공통된 동작을 제 3의 함수에 분리해 놓고 양쪽에서 호출하도록 하자!


🖊️ 복사 함수

== 복사 생성자, 복사 대입 연산자

컴파일러가 만들어준 기본 복사 함수들은 복사되는 객체가 갖고 있는 데이터를 빠짐없이 복사한다.

void logCall(const string& funcName);

class Customer {
public:
	...
    Customer(const Customer& rhs);
    Customer& operator = (const Customer& rhs);
    ...
private:
	string name;
};

Customer::Customer(const Customer& rhs) : name(rhs.name) {
	logCall("Customer Copy Constructor");
}

Customer& Customer::operator= (const Customer& rhs) {
	logCall("Customer Copy Assignment Operator");
    name = rhs.name;
    return *this;
}

이렇게 멀쩡한 Customer 클래스에..

데이터 멤버를 추가하면?

class Date { ... };
class Customer {
public:
	...
private:
	string name;
    Date lastTransaction;
};

Date 클래스 멤버변수 하나를 추가해줬을 뿐인데,
복사 생성자와 복사 대입 연산자는 전체 복사가 아닌 일부 복사만 하는 바보같은 함수가 되어버렸다.
이 상황에서 컴파일러는 아무런 경고도 해주지 않는다
그냥 바보가 된 채로 멀쩡히 돌아간다...

이렇게 데이터 변수를 추가하면
내가 직접.. 복사 함수를 다시 작성해줘야된다.
근데 이제 문제는
...
1. 복사 생성자, 복사 대입 연산자 다시 작성
2. 생성자 갱신 (모두 초기화 해야 함)
3. 비표준형 operator= 함수도 바꿔줘야 함

휴...


🖊️ 상속에서의 복사 함수들

class PriorityCustomer : public Customer {
public:
	...
    PriorityCustomer(const PriorityCustomer& rhs);
    PriorityCUstomer& operator= (const PriorityCustomer& rhs);
    ...
private:
	int priority;
};

PriorityCustomer::PriorityCustomer (const PriorityCustomer& rhs) : priority(rhs.priority) {
	logCall("Priority Customer Copy Consturctor");
}

PriorityCustomer& PriorityCustomer::operator= (const PriorityCustomer* rhs) {
	logCall("Priority Customer Copy Assignment Operator");
    priority = rhs.priority;
    return *this;
}

이러한 상황에서..

문제점

복사 생성자, 복사 대입 연산자들은 PriorityCustomer에 선언된 데이터 멤버들을 모두 복사하고 있지만, Customer에서 상속받은 데이터 멤버들은 복사하지 않고 있다!!!!!!!

복사 생성자

PriorityCustomer의 복사 생성자에는 기본 클래스 생성자에 넘길 인자들이 명시되어 있지 않다.
그래서 PriorityCustomer 객체의 Customer 부분은 기본 생성자에 의해 초기화될거다.

복사 대입 연산자

기본 클래스의 데이터 멤버는 복사 대입 연산자를 사용하더라도 변경되지 않을거다.

해결 방법

파생클래스의 복사함수 안에서 기본 클래스의 복사함수를 호출하도록 만들자!

PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs) 
	: Customer(rhs), priority(rhs.priority)
    {
    	logCall("PriorityCustomer Copy Constructor");
    }
    
PriorityCustomer& PriorityCustomer::operator= (const PriorityCustomer& rhs) {
	logCall("PriorityCustomer Copy Assignment Operator");
    Customer::operator=(rhs);
    priority = rhs.priority;
    return *this;
}

복사 생성자에서는 Customer(기본 클래스)의 복사 생성자를 호출해주고
복사 대입 연산자에서는 Customer(기본 클래스)의 복사 대입 연산자를 호출해줬다.

따라서...

  1. 해당 클래스의 데이터 멤버를 모두 복사하고
  2. 이 클래스가 상속한 기본 클래스의 복사 함수를 호출해줘야 한다!!!

🖊️ 복사 함수에서 복사 함수 호출?

복사 생성자 : 새로운 객체를 생성하고 초기화 하는 것
복사 대입 연산자 : 초기화된 객체에 새로운 값을 넣는 것

따라서

복사 생성자를 복사 대입 연산자에서 호출하면
초기화된 객체에 대해 굳이 새로운 객체를 생성할 필요가 없음.
말이 안됨

복사 대입 연산자를 복사 생성자에서 호출하면
생성중인 객체에 대입하면,, 초기화 안된 객체에 대해 새로운 값을 넣어준다는 거임.
말이 안됨

ㅋㅋ

그냥 겹치는 부분을 Init어쩌구~ private 함수로 만들어서
그 함수를 복사 생성자, 복사 대입 연산자 양쪽에서 호출하게 만들자


😊 느낀점


생각도 못한 부분이다
이 책을 읽으면서 항상 생각하지만..
너무 당연한 내용이지만 막상 내가 코딩할 때는 생각도 안난다
적용을 못해봤다..
이렇게 상속된 클래스에서 기본 클래스의 멤버변수를 초기화해주지 않는 것에 대한 문제
기본 클래스의 생성자를 호출해줘야 한다는 것
너무나 당연한데 생각을 못햇던 것
바부

0개의 댓글