이미 잘 설정된 객체가 있다면 같은 부분을 복제하는 것이 가장 쉽다. 예를 들면,
Contact john{ "John Doe", Address{"123 East Dr", "London", 10} };
Contact jane{ "Jane Doe", Address{"123 East Dr", "London", 11} };
에서 john과 jane은 사무실의 방만 다르고 같은 빌딩에서 일한다(cc인 듯). 아마 같은 회사에서 일하는 사람들도 주소가 같을 것이다. 즉 수많은 객체가 같은 값으로 중복되게 초기화되는 작업이 발생한다. 어떻게 반복된 중복 설정을 피할 수 있을까?
예를 들어 연락처와 주소를 아래와 같이 정의하자
struct Address{
string street;
string city;
int suite;
}
struct Contact{
string name;
Address address;
}
...
Contact worker{"", Address{"123 East Dr", "London", 0}}; //prototype instance
Contact john = worker; //copy and change some data
john.name = "John Doe";
john.address.suite = 10;
위 코드에서 worker
객체를 선언한 후에 일부 데이터를 수정했다. 만약 Address
객체가 아래와 같이 포인터로 되어있다면?
struct Contact{
string name;
Address *address; //or shared_ptr
}
...
Contact john = prototype; //copy address pointer from prototype to john
위 코드의 경우 prototype의 Address
포인터가 john으로 복제되었기 때문에 의도치 않은 복사 문제를 발생시킬 수 있다.
만약 4.2의 마지막처럼 Address
를 포인터로 저장한다면 객체의 복제 생성자를 잘 정의하면 된다.
첫 번째 방법은 아래와 같다.
Contact(const Contact& other): name{other.name} //initialize name from other
{ //make new Address instance
address = new Address(
other.address->street,
other.address->city,
other.address->suite
);
}
단 위의 경우 other
의 멤버 하나하나마다 일일히 값을 집어넣기 때문에 Address
의 항목이 바뀌면 일일히 추가해야 한다. 이를 해결하기 위해 Address
의 복제 생성자를 정의하자.
Address(const string& street, const string& city, const int suite)
: street(street),
city{city},
suite{suite} {}
...
Contact(const Contact& other)
: name{other.name}
, address{ new Address{*other.address}} {}
...
Contact& operator=(const Contact& other){
if (this == &other)
return *this;
name = other.name;
address = other.address;
return *this;
}
이전 코드와 비교했을 때 일일히 other.address->xx
를 이용해 객체를 초기화할 필요가 없어진다.
단, 위 코드는 아래와 같은 문제점이 있다.
1. 복제 생성자를 하나하나 구현해야 한다.
2. 복제 생성자나 대입 연산자 구현이 누락되더라도 컴파일이 되기 때문에 나중에 문제가 될 수 있다.
3. 만약 shared_ptr
이 아니라 이중 포인터 혹은 unique_ptr
을 사용한다면 구현이 조금 더 복잡해진다.
다른 언어에서는 클래스를 직렬화 할 수 있어서 쉽게 복제가 가능하다. 하지만 C++에서는 직렬화를 지원하지 않는다.
단, Boost
라이브러리의 Boost.Serialization
을 이용하면 직렬화 할 수 있다.
개인적인 의견:
Boost
는 C++ 프로젝트에서 잘 사용되지 않는 것 같다. 우선 표준 C++에 통합된 경우 추후에 코드 수정이 필요하며, 현업에서는 신뢰성의 문제로 잘 사용하지 않는 것 같다.
이하 생략...
자주 사용할 객체들이 미리 정해져 있다면 아래와 같이 전역 변수에 저장해 복사하는 방식으로 사용할 수 있다.
/* Contact.hpp */
Contact main{ "", new Address{ "123 East Dr", "London", 0 } };
Contact aux{ "", new Address{ "123B East Dr", "London", 0 } };
하지만 목적에 맞는 복제본을 요구받는 순간에 객체를 만들어 제공하는 것이 훨씬 깔끔하다. 예를 들면, 아래 코드에서는 객체의 unique_ptr
을 리턴하는 편의 함수가 제공된다.
struct EmployeeFactory{
static Contact main;
static Contact aux;
static unique_ptr<Contact> NewMainOfficeEmployee(string name, int suit){
return NewEmployee(name, suit, main);
}
static unique_ptr<Contact> NewAuxOfficeEmployee(string name, int suit){
return NewEmployee(name, suit, aux);
}
private:
static unique_ptr<Contact> NewEmployee(string name, int suite, Contact& proto){
auto result = make_unique<Contact>(proto);
result->name = name;
result->address->suite = suite;
return result;
}
}
...
auto john = EmployeeFactory::NewAuxOfficeEmployee("John Doe", 123);
auto jane = EmployeeFactory::NewMainOfficeEmployee("Jane Doe", 125);
위처럼 팩터리를 이용하면, 새로 설정해야 하는 부분의 누락을 방지할 수 있다.