일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- 그루비 스타일 빌더
- 동적 데코레이터
- 데커레이터
- 브릿지
- 추상 팩터리
- 데커레이터 패턴
- 팩터리
- 동적 데커레이터
- 모던C++디자인패턴
- 흐름식 빌더
- 디자인 패턴
- 컴포지트
- 프로토타입
- 내부 팩터리
- 싱글톤 패턴
- 디자인패턴
- 프로토타입 패턴
- 브릿지 패턴
- 함수형 팩터리
- 싱글턴 패턴
- 단순한 빌더
- 컴포지트 패턴
- 팩터리 패턴
- 빌더 패턴
- 프로토타입 중복처리
- 싱글톤
- 싱글턴
- 팩터리 메서드
- 컴포지트 빌더
- 빌더
- Today
- Total
GGym's Practice Notes
4-1. 프로토타입_중복처리 본문
# 객체 생성
아래의 두 객체는 중복된 부분이 있다.
contact john{ "John Doe", Address{"123 East Dr", "London", 10}};
contact jane{ "Jane Doe", Address{"123 East Dr", "London", 11}};
john과 jane은 사무실 방만 다르고 같은 건물에서 일하고 있다. (다른 사람들도)
수많은 객체가 같은 값으로 중복되게 초기화 되는 작업이 발생한다.
프로토타입 패턴은 객체의 복제가 주요기능이다.
# 평범한 중복처리
복제의 목적이 값을 사용하는 것에 있고, 복제 대상 객체의 모든 항목이 값으로만 되어있다면
복제하는데 문제 될 것이 없다. 예로 연락처와 주소가 다음과 같이 정의되어 있다면,
struct Address{
string street, city;
int suite;
};
struct Contact{
string name;
Address address;
};
아래와 같은 코드를 사용하는데 문제가 없다.
// 프로토타입 객체
Contact worker{"", Address{"123 East Dr", "London", 0}};
// 프로토타입 복제하고 일부 수정
Contact john = worker;
john.name = "John Doe";
john.address.suite = 10;
실제로 이렇게 쉬운 경우는 드물다. Contact의 Address 객체가 포인터(또는 shared_ptr)로 된 경우가 많다.
이 부분은 문제가 있다. Contact john = prototype 코드가 수행될 때 포인터가 복제되기 때문에 둘다 같은 객체를 가지게 된다.
# 복제 생성자를 통한 중복처리
복제 생성자를 정의하는데 두가지 방법이 있다. 쉽게는 아래와 같이 구현한다.
struct Contact{
string name;
shared_ptr<Address> address;
Contact(const Contact& other)
: name{other.name}{
address = make_shared<Address>(
other.address->street,
other.address->city,
other.address->suite);
}
};
주소의 항목이 변경되면 사용할 수 없으므로 위 코드는 범용적이지 않다.
Address에 복사생성자를 정의하고 Contact의 생성자를 재활용하여 사용할 수 있다.
Address(const string& street, const string& city, const int suite)
: street{street}, city{city}, suite{suite}{}
Contact(const Contact& other)
: name{name}, address{make_shared<Address>(*other.address)}{
}
ReSharper의 코드 생성기를 사용하면 복제와 이동 연산 코드를 자동으로 생성해준다. (책에서는 이러한 부가기능들에 대해 소개를 하지만 ReSharper는 유료고 비싸다(개인용 129달러/1년).. 직접 구현하자.)
operator= 를 아래와 같이 구현한다.
Contact& operator=(const Contact& other){
if(this == &other)
return *this;
name = other.name;
address = other.address;
return *this;
}
이제 프로토타입을 생성하면서 안전하게 재사용할 수 있다.
Contact worker{"", make_shared<Address>("123 East Dr", "London", 0)};
Contact john{worker}; // or Contact john = worker;
john.name = "John";
john.address->suite = 10;
이 방법의 유일한 문제는 온갖 복제 생성자를 하나하나 구현하는데 오래걸린다는 점이다. 예를 들어 다음과 같은 코드가 작성되었다고 하자.
Contact john = worker;
그리고 Address에 복제 생성자와 대입 연산자의 구현이 누락되었다고 하자(단 Contact는 구현되어 있다). 이 코드는 컴파일되는데 아무런 문제가 없다.
복사생성자만 사용된다면 상황이 조금 낫다. 존재하지 않는 복사생성자의 호출이 있으면 에러가 발생하여 문제를 인지할 수 있다.
하지만 대입 연산자는 디폴트 동작이 정해져 있으므로 적절한 대입연산자를 정의하지 않았더라도 컴파일되어 실행된다.
또 다른 문제로 이중 포인터 또는 unique_ptr를 사용한다면 코드 자동 생성을 사용할 시에 문제가 생길 수 있다.
전체 코드:
#include<iostream>
#include<memory>
using namespace std;
struct Address{
string street, city;
int suite;
Address(const string& street, const string& city, const int suite)
: street{street}, city{city}, suite{suite}{}
};
struct Contact{
string name;
shared_ptr<Address> address;
Contact(const Contact& other)
: name{name}, address{make_shared<Address>(*other.address)}{
}
Contact& operator=(const Contact& other){
if(this == &other)
return *this;
name = other.name;
address = other.address;
return *this;
}
Contact(const string name, const shared_ptr<Address> address)
: name{name}, address{address}{
}
};
int main(){
Contact worker{"", make_shared<Address>("123 East Dr", "London", 0)};
Contact john{worker}; // or Contact john = worker;
john.name = "John";
john.address->suite = 10;
return 0;
}
'Design Pattern > Modern C++ 디자인패턴' 카테고리의 다른 글
4-3. 프로토타입_프로토타입 팩터리 (0) | 2020.04.22 |
---|---|
4-2. 프로토타입_직렬화 (0) | 2020.04.17 |
3-2. 팩터리_추상 팩터리/함수형 팩터리 (0) | 2020.04.02 |
3-1. 팩터리_팩터리 메서드/팩터리/내부 팩터리 (0) | 2020.04.02 |
2-3. 빌더_컴포지트 빌더 (0) | 2020.03.31 |