복사 생성자(copy constructor)
복사 생성자는 자신과 같은 클래스 타입의 객체의 복사본을 생성할 때 다른 객체의 참조(reference)를 매개변수로 받아서, 그 참조를 통해 자신(객체)을 초기화하는 방법이다.
보통의 복사와는 다르게 복사생성자는 복사된 객체가 원본 객체와 같으면서 완전한 독립성을 띄고 있다.
바로 깊은 복사(deep copy)를 통한 복사이기 때문인데, 밑의 사진을 보자.
우선 얕은 복사(Shallow Copy)란, 대입 연산자(=)를 통한 변수에 다른 변수의 값을 대입하는 방식을 말한다.
//Shallow Copy(앝은 복사)
int x = 10;
int y = x;
x= 21;
cout << x << y;
위 예시처럼 변수 y에 x를 대입해줌으로써 변수 x의 값 10을 변수 y에 복사하여 10이 대입되었다.
그런데 중간에 변수 x의 값을 변경하면 변수 y의 값 10도 바뀐다. 이는 y값의 원본은 결국 x를 가리키고 있기 때문이다.
이를 통해 얕은 복사는 값을 복사하는 것이 아닌, 값을 가리키는 포인터를 복사하는 것을 알 수 있다.
또한 원본 값인 x가 삭제된다면 변수 y의 값을 출력할 때 오류가 발생하는 등 몇 가지 문제점이 있는데, 이러한 문제점을
해결하기 위해 깊은 복사(Deep Copy)의 방법이 있다.
깊은 복사(Deep Copy)는 원본과 복사본이 서로 다른 메모리 공간을 참조하고 있다.
class Example
{
string name;
int age;
Example() {};
~Example() {};
//복사생성자
Example(const Example& src)
{
name = src.name;
age = src.age;
}
};
void main()
{
//기본생성자 호출
Example ex1;
//복사생성자 호출
Example ex2 = ex1; //Example ex2 = Example(ex1);과 같은 뜻이다.
}
복사 생성자를 통해 복사하는 것이 바로 깊은 복사라고 하는데, 클래스를 작성할 때 사용할 수 있는 생성자들 중에 하나다.
클래스 이름(const 클래스 이름& 매개변수이름);
복사 생성자를 생성하는 방법은 이처럼 생성해주면 된다.
저렇게 같은 클래스 타입의 객체에 대입해주고자 할 때 복사 생성자가 호출이 되는 것을 볼 수 있다.
원본 ex1을 ex2가 복사하여 생성되는 것인데, ex1에 대한 참조를 &src가 하는 것을 코드를 통해 알 수 있다.
이렇게 복사 생성자를 통해 깊은 복사를 하고 나면 원본 ex1의 값을 변경해도 ex2의 값은 변하지 않는 것을 알 수 있다.
애초에 서로 가리키는 주소 자체가 다르므로 한쪽의 변경이 전혀 영향을 미치지 않는 것이다.
팩토리 패턴(Factory Pattern)
디자인 패턴 중의 하나인 팩토리 패턴은 객체를 생성하는 작업이 복잡하고 어렵거나 절차를 따라야 할 경우 이를 대행하는 함수를 만드는 설계 방식이다. 즉, 객체를 생성하기 위해 필요한 인터페이스를 만든 후, 인터페이스를 구현하는 클래스에서 어떤 객체를 만들지 결정하는 패턴을 말한다.
그렇게 만든 함수를 팩토리 메서드(factory method) 또는 가상 생성자(virtual constructor)라고도 부르는데, 이 함수는
객체를 대신 생성하여 전달해주는 역할을 한다.
팩토리에는 몇 가지 방법이 존재한다.
내부 팩토리
팩토리 메서드를 좀 더 분명히 하기 위해 따로 팩토리 클래스로 묶어서 모아둔 것을 말한다.
class Point
{
float x, y;
protected:
Point(float x, float y)
: x { x }, y { y } { }
public:
//내부 팩토리
class Factory
{
static Point CreateCartesian(float x, float y)
{
return { x, y };
}
static Point CreatePolar(float distance, float radian)
{
return { distance * cos(radian), distance * sin(radian) };
}
}
};
Point p = Point::Factory::CreateCartesian(3.4, 4.5);
외부 팩토리
클래스 내부에 팩토리를 만들어 놓으면 API 사용성이 좋아지지만, 여러 타입을 이용해야 할 때는 클래스 외부에 만드는
것이 구현에 있어서 깔끔해진다.
팩토리에서 클래스 내부에 있는 생성자를 접근해야 하기 때문에 friend 선언이 필요하다.
이와 같은 외부 팩토리를 매개변수 기반 팩토리 메서드(parametrized factory method)라고 부른다.
class A; class B; class C;
class D
{
friend class Factory;
A a;
B b;
C c;
protected:
D(A a, B b, C c);
};
//외부 팩토리
class Factory
{
public:
static D CreateD(A a, B b, C c)
{
return { a, b, c };
}
};
추상 팩토리
여러 타입의 군(family)을 생성할 때 사용한다. 어떤 타입을 만들 것인지 지정하지 않아도 연관된 혹은 독립된 객체들의
군을 생성할 수 있는 인터페이스를 가지고 있다.
추상 팩토리를 사용할 때의 주의할 점은 구체 팩토리를 만들어야 사용할 수 있다는 것이다.
class Beverage
{
public:
virtual void Prepare() = 0;
};
// 아래 두 클래스는 Prepare()을 적절히 재정의 했다고 가정한다.
class Coffee : public Beverage { };
class Tea : public Beverage { };
// 추상 팩토리
// 실제로 사용하려면 구체 팩토리가 있어야 한다.
class BeverageFactory
{
public:
unique_ptr<Beverage> Make() const = 0;
};
// 구체 팩토리
class CoffeeFactory : public BeverageFactory { };
class TeaFactory : public BeverageFactory { };
// 추상 팩토리 사용례
class Store
{
unordered_map<string, unique_ptr<BeverageFactory>> beverageFactories;
public:
Store()
{
beverageFactories["Coffee"] = make_unique<CoffeeFactory>();
beverageFactories["Tea"] = make_unique<TeaFactory>();
}
unique_ptr<Beverage> GetDrink(const string& kind)
{
return beverageFactories[kind]->Make();
}
};
함수형 팩토리
팩토리 메서드를 함수형으로도 만들 수 있는데, 호출 객체(callable object)를 사용하면 된다.
class Store
{
unordered_map<string, unique_ptr<BeverageFactory>> beverageFactories;
public:
Store()
{
beverageFactories["Coffee"] = [] { return make_unique<Coffee>(); };
beverageFactories["Tea"] = [] { return make_unique<Tea>(); }
}
unique_ptr<Beverage> GetDrink(const string& kind)
{
return beverageFactories[kind]->Make();
}
};
팩토리는 생성자를 대신해서 객체를 생성해주는 디자인 패턴이었다. 어떤 구체적 클래스의 인스턴스가 생성되는지
캡슐화해주면 어떻게 생성하는지 숨겨줄 수 있다.
팩토리 패턴의 장점으로는,
- 객체 생성과 관련된 과정들을 추상화시킬 수 있다.
- 객체 생성과 관련된 최적화와 제한을 할 수 있다.
- 가독성이 향상된다.
- 여러 종류의 객체 중 하나를 생성할 수 있다.
와 같은 장점들이 있다.
하지만 단점으로는,
- 팩토리와 대상과의 결합도가 높아져서 문제가 발생할 수 있다.
- 새로운 종류의 객체를 생성하고자 할 때는, 기존의 코드를 수정해야 할 때도 있다.
- 추상 팩토리에서 새로운 객체를 추가하는 것이 어렵다.
와 같은 단점들이 있다.
참고자료
http://www.tcpschool.com/cpp/cpp_conDestructor_copyConstructor
https://coding-factory.tistory.com/701
https://haedallog.tistory.com/159
https://cinrueom.tistory.com/34
등등...
'C++' 카테고리의 다른 글
C++ 배우기 27(함수포인터) (0) | 2022.12.01 |
---|---|
C++ 배우기 26(LValue / RValue) (0) | 2022.11.30 |
C++ 배우기 24(가상함수, 재정의) (0) | 2022.11.25 |
C++ 배우기 23(생성자, 소멸자) (0) | 2022.11.24 |
C++ 배우기 22(static) (0) | 2022.11.23 |