C ++에서 팩토리 메소드 패턴을 올바르게 구현하는 방법


329

C ++에는 이것이 단순하게 들리지만 정직하게 수행하는 방법을 알지 못하기 때문에 오랫동안 편안하게 생겼습니다.

C ++에서 팩토리 메소드를 올바르게 구현하려면 어떻게해야합니까?

목표 : 허용 할 수없는 결과와 성능 저하없이 클라이언트가 객체의 생성자 대신 팩토리 메소드를 사용하여 일부 객체를 인스턴스화 할 수 있도록합니다.

"팩토리 메소드 패턴"이란 객체 내부의 정적 팩토리 메소드 또는 다른 클래스에 정의 된 메소드 또는 전역 함수를 의미합니다. 일반적으로 "클래스 X의 일반적인 인스턴스화 방법을 생성자 이외의 다른 곳으로 리디렉션하는 개념".

내가 생각한 몇 가지 가능한 답변을 살펴 보도록하겠습니다.


0) 팩토리를 만들거나 생성자를 만들지 마십시오.

이것은 훌륭하게 들리며 (실제로는 종종 최상의 솔루션 임) 일반적인 치료법은 아닙니다. 우선, 객체 구성이 다른 클래스로의 추출을 정당화하기에 충분히 복잡한 작업 인 경우가 있습니다. 그러나 생성자를 사용하는 간단한 객체의 경우에도 그 사실을 제쳐두기도합니다.

내가 아는 가장 간단한 예는 2-D Vector 클래스입니다. 간단하지만 까다 롭습니다. 직교 좌표와 극좌표 모두에서 구성 할 수 있기를 원합니다. 분명히, 나는 할 수 없습니다 :

struct Vec2 {
    Vec2(float x, float y);
    Vec2(float angle, float magnitude); // not a valid overload!
    // ...
};

내 자연스러운 사고 방식은 다음과 같습니다.

struct Vec2 {
    static Vec2 fromLinear(float x, float y);
    static Vec2 fromPolar(float angle, float magnitude);
    // ...
};

이것은 생성자 대신 정적 팩토리 메소드를 사용하게합니다. 이것은 기본적으로 팩토리 패턴을 어떤 방식 으로든 구현하고 있음을 의미합니다 ( "클래스가 자체 팩토리가 됨"). 이것은 멋지게 보이며 (이 특별한 경우에 적합 할 것입니다) 어떤 경우에는 실패합니다. 포인트 2에서 설명하겠습니다. 계속 읽으십시오.

또 다른 경우 : 관련이없는 도메인의 GUID 또는 GUID 및 비트 필드와 같은 일부 API의 두 가지 불투명 한 typedef로 과부하를 시도하면 유형이 의미 상 완전히 다른 (이론적으로는 유효한 과부하) 유형이지만 실제로는 부호없는 int 또는 void 포인터와 같은 것.


1) 자바 웨이

우리는 동적 할당 객체 만 가지고 있기 때문에 Java는 간단합니다. 공장을 만드는 것은 다음과 같이 사소합니다.

class FooFactory {
    public Foo createFooInSomeWay() {
        // can be a static method as well,
        //  if we don't need the factory to provide its own object semantics
        //  and just serve as a group of methods
        return new Foo(some, args);
    }
}

C ++에서 이것은 다음과 같이 번역됩니다.

class FooFactory {
public:
    Foo* createFooInSomeWay() {
        return new Foo(some, args);
    }
};

멋있는? 종종, 실제로. 그러나 이렇게하면 사용자가 동적 할당 만 사용하게됩니다. 정적 할당은 C ++를 복잡하게 만드는 요소이지만 종종 강력하게 만드는 요소이기도합니다. 또한 동적 할당을 허용하지 않는 일부 대상 (키워드 : 포함)이 있다고 생각합니다. 그렇다고 해당 플랫폼의 사용자가 깨끗한 OOP를 작성하는 것을 좋아하지는 않습니다.

어쨌든, 철학은 제쳐두고 : 일반적인 경우에, 나는 공장 사용자들이 동적 할당에 구속되도록 강요하고 싶지 않다.


2) 가치 별 반품

우리는 동적 할당을 원할 때 1) 멋지다는 것을 알고 있습니다. 왜 우리는 정적 할당을 추가하지 않습니까?

class FooFactory {
public:
    Foo* createFooInSomeWay() {
        return new Foo(some, args);
    }
    Foo createFooInSomeWay() {
        return Foo(some, args);
    }
};

뭐? 반품 유형에 의해 과부하가 걸리지 않습니까? 물론 우리는 할 수 없습니다. 이를 반영하기 위해 메소드 이름을 변경해 봅시다. 그리고 예, 나는 이름을 변경해야하기 때문에 현재 언어와 무관 한 팩토리 디자인을 구현할 수 없기 때문에 메소드 이름을 변경 해야하는 필요성을 얼마나 싫어하는지 강조하기 위해 위의 잘못된 코드 예제를 작성했습니다. 이 코드의 모든 사용자는 사양과 구현의 차이점을 기억해야합니다.

class FooFactory {
public:
    Foo* createDynamicFooInSomeWay() {
        return new Foo(some, args);
    }
    Foo createFooObjectInSomeWay() {
        return Foo(some, args);
    }
};

알았어. 저기있다. 메소드 이름을 변경해야하기 때문에 추악합니다. 동일한 코드를 두 번 작성해야하므로 불완전합니다. 그러나 일단 완료되면 작동합니다. 권리?

보통은 그러나 때로는 그렇지 않습니다. Foo를 만들 때 C ++ 표준은 컴파일러 공급 업체가 객체를 내부에서 언제 만들지, 언제 반환 할지를 지정할 수 없을 정도로 자비하기 때문에 실제로 컴파일러를 사용하여 반환 값 최적화를 수행합니다. C ++에서 값별 임시 객체. 따라서 Foo가 복사 비용이 많이 드는 경우이 방법은 위험합니다.

Foo를 전혀 복사 할 수없는 경우 어떻게해야합니까? 글쎄요 ( 복사본 제거가 보장 된 C ++ 17에서 복사 할 수 없음은 위 코드에서 더 이상 문제가되지 않습니다. )

결론 : 객체를 반환하여 팩토리를 만드는 것은 실제로 (앞서 언급 한 2D 벡터와 같은) 일부 솔루션이지만 여전히 생성자를 대체하지는 않습니다.


3) 2 상 구조

누군가가 생각해 낼 수있는 또 다른 것은 객체 할당과 초기화 문제를 분리하는 것입니다. 일반적으로 다음과 같은 코드가 생성됩니다.

class Foo {
public:
    Foo() {
        // empty or almost empty
    }
    // ...
};

class FooFactory {
public:
    void createFooInSomeWay(Foo& foo, some, args);
};

void clientCode() {
    Foo staticFoo;
    auto_ptr<Foo> dynamicFoo = new Foo();
    FooFactory factory;
    factory.createFooInSomeWay(&staticFoo);
    factory.createFooInSomeWay(&dynamicFoo.get());
    // ...
}

매력처럼 작동한다고 생각할 수도 있습니다. 우리 코드에서 지불하는 유일한 가격은 ...

이 모든 것을 작성하고 이것을 마지막으로 남겨 두었으므로 나는 그것을 싫어해야합니다. :) 왜?

우선 ... 나는 2 단계 구조의 개념을 진심으로 싫어하고 그것을 사용할 때 유죄를 느낍니다. "있는 경우 유효한 상태"라는 어설 션으로 개체를 디자인하면 코드가 더 안전하고 오류가 덜 발생합니다. 그 방법이 마음에 들어요.

그 관습을 버리고 물건의 공장을 만들기위한 목적으로 만 물건의 디자인을 바꾸는 것은 .. 음, 다루기 힘들다.

위의 내용이 많은 사람들을 설득하지는 않는다는 것을 알고 있으므로 좀 더 확실한 주장을하겠습니다. 2 단계 구성을 사용하면 다음을 수행 할 수 없습니다.

  • const멤버 변수 초기화 또는 참조
  • 기본 클래스 생성자와 멤버 객체 생성자에 인수를 전달합니다.

그리고 아마도 내가 지금 생각할 수없는 몇 가지 단점이있을 수 있으며, 위의 글 머리 기호가 이미 저를 설득했기 때문에 특별히 의무감을 느끼지 않습니다.

따라서 공장 구현을위한 훌륭한 일반 솔루션에 가깝지 않습니다.


결론 :

우리는 다음과 같은 객체 인스턴스화 방법을 원합니다.

  • 할당에 관계없이 균일 한 인스턴스화를 허용합니다.
  • 건설 방법에 다른 의미있는 이름을 부여하십시오 (따라서 인수 오버로드에 의존하지 않음)
  • 특히 클라이언트 측에서 현저한 성능 저하, 바람직하게는 상당한 코드 증가를 유발하지 않습니다.
  • 다음과 같이 일반적이어야합니다 : 모든 수업에 도입 될 수 있습니다.

내가 언급 한 방법이 이러한 요구 사항을 충족하지 못한다는 것이 입증되었습니다.

힌트가 있습니까? 해결책을 알려주세요.이 언어가 그런 사소한 개념을 제대로 구현할 수 없다고 생각하고 싶지 않습니다.


7
@Zac은 제목이 매우 유사하지만 실제 질문은 IMHO와 다릅니다.
Péter Török

2
좋은 복제본이지만 질문 의 텍스트 는 그 자체로 가치가 있습니다.
dmckee --- 전 변조 고양이

7
이 질문을 한 지 2 년 후에는 다음과 같은 사항을 추가해야합니다. 1) 이 질문은 몇 가지 디자인 패턴과 관련이 있습니다. 2) 여기서 논의되는 실제 문제는 "객체 저장소 할당을 객체 구성에서 깔끔하게 분리하는 방법"입니다.
코스

1
@ 데니스 : 당신이하지 않은 경우에만 delete. 호출자가 포인터의 소유권을 가져 오는 "문서화 된"(소스 코드는 문서 ;-)) 이러한 종류의 메소드는 완벽하게 좋습니다.
보리스 달 스타 인

1
@Boris @Dennis unique_ptr<T>대신을 반환하여 매우 명시 적으로 만들 수도 있습니다 T*.
코스

답변:


107

우선, 객체 구성이 다른 클래스로의 추출을 정당화하기에 충분히 복잡한 작업 인 경우가 있습니다.

나는이 점이 틀렸다고 믿는다. 복잡성은 실제로 중요하지 않습니다. 관련성이 있습니다. 빌더 패턴과 달리 한 단계로 오브젝트를 구성 할 수있는 경우 생성자가 올바른 위치에 있습니다. 작업을 수행하기 위해 다른 클래스가 실제로 필요한 경우 어쨌든 생성자에서 사용되는 도우미 클래스 여야합니다.

Vec2(float x, float y);
Vec2(float angle, float magnitude); // not a valid overload!

이에 대한 쉬운 해결 방법이 있습니다.

struct Cartesian {
  inline Cartesian(float x, float y): x(x), y(y) {}
  float x, y;
};
struct Polar {
  inline Polar(float angle, float magnitude): angle(angle), magnitude(magnitude) {}
  float angle, magnitude;
};
Vec2(const Cartesian &cartesian);
Vec2(const Polar &polar);

유일한 단점은 조금 장황하게 보인다는 것입니다.

Vec2 v2(Vec2::Cartesian(3.0f, 4.0f));

그러나 좋은 점은 사용중인 좌표 유형을 즉시 볼 수 있으며 동시에 복사에 대해 걱정할 필요가 없다는 것입니다. 복사를 원하고 비용이 많이 든다면 (물론 프로파일 링에서 입증 된 바와 같이) 복사 오버 헤드를 피하기 위해 Qt의 공유 클래스 와 같은 것을 사용할 수 있습니다 .

할당 유형의 경우 팩토리 패턴을 사용하는 주된 이유는 일반적으로 다형성입니다. 생성자는 가상 일 수 없으며, 가능하더라도 아무리 이해가되지 않습니다. 정적 또는 스택 할당을 사용하는 경우 컴파일러에서 정확한 크기를 알아야하기 때문에 다형성 방식으로 객체를 만들 수 없습니다. 따라서 포인터와 참조에서만 작동합니다. 객체가 기술적으로 동안 때문에 공장에서 참조를 반환하는 것은, 너무 작동하지 않을 수 있습니다 참조에 의해 삭제 될,이 참조가 혼란 버그가 발생하기 쉬운 대신 할 수 는 C ++ 참조 변수, 악을 반환하는 관행인가?예를 들어. 따라서 포인터는 남은 유일한 것이며 스마트 포인터도 포함됩니다. 즉, 팩토리는 동적 할당과 함께 사용할 때 가장 유용하므로 다음과 같은 작업을 수행 할 수 있습니다.

class Abstract {
  public:
    virtual void do() = 0;
};

class Factory {
  public:
    Abstract *create();
};

Factory f;
Abstract *a = f.create();
a->do();

다른 경우에는 공장에서 언급 한 과부하 문제와 같은 사소한 문제를 해결하는 데 도움이됩니다. 그것들을 균일하게 사용하는 것이 가능하다면 좋을 것입니다. 그러나 아마도 불가능하다는 것은 크게 아프지 않습니다.


21
직교 및 극좌표 구조의 경우 +1 일반적으로 Vec 구조체와 달리 의도 한 데이터를 직접 나타내는 클래스와 구조체를 만드는 것이 가장 좋습니다. 귀하의 공장도 좋은 예이지만 귀하의 예는 누가 포인터 'a'를 소유하고 있는지를 보여주지 않습니다. 팩토리 'f'가 소유하고 있다면 'f'가 범위를 벗어나면 소멸 될 수 있지만 'f'가 소유하지 않으면 개발자가 해당 메모리를 해제하거나 메모리 누수가 발생할 수 있음을 기억하는 것이 중요합니다 나오다.
David Peterson

1
물론 객체는 참조로 삭제할 수 있습니다! stackoverflow.com/a/752699/404734 참조 물론 복사로 반환 값을 할당 할 수있는 문제 때문에 호출자가 동적 메모리를 참조로 반환하는 것이 현명한 지 여부는 의문을 제기합니다 (호출자는 또한 무언가를 할 수 있습니다) int a = * returnsAPoninterToInt ()와 같이 동적으로 할당 된 메모리가 반환되는 경우 참조와 같이 동일한 문제에 직면하지만 포인터 버전에서 사용자는 명시 적으로 참조하는 것을 잊어 버리는 대신 명시 적으로 역 참조해야합니다. .
Kaiserludi

1
@ Kaiserludi, 좋은 지적. 나는 그것을 생각하지 않았지만 여전히 일을하는 "악한"방법입니다. 그것을 반영하기 위해 내 대답을 편집했습니다.
Sergei Tachenov

변경 불가능한 다른 비다 형성 클래스를 생성하는 것은 어떻습니까? 팩토리 패턴은 C ++에서 사용하기에 적합합니까?
daaxix

@daaxix, 왜 비 다형성 클래스의 인스턴스를 만들기 위해 팩토리가 필요합니까? 불변이 이것과 어떤 관련이 있는지 알 수 없습니다.
Sergei Tachenov

49

간단한 공장 예 :

// Factory returns object and ownership
// Caller responsible for deletion.
#include <memory>
class FactoryReleaseOwnership{
  public:
    std::unique_ptr<Foo> createFooInSomeWay(){
      return std::unique_ptr<Foo>(new Foo(some, args));
    }
};

// Factory retains object ownership
// Thus returning a reference.
#include <boost/ptr_container/ptr_vector.hpp>
class FactoryRetainOwnership{
  boost::ptr_vector<Foo>  myFoo;
  public:
    Foo& createFooInSomeWay(){
      // Must take care that factory last longer than all references.
      // Could make myFoo static so it last as long as the application.
      myFoo.push_back(new Foo(some, args));
      return myFoo.back();
    }
};

2
@LokiAstari 스마트 포인터를 사용하는 것이 메모리 제어를 잃는 가장 간단한 방법이기 때문입니다. 다른 C / C ++ 언어에 대한 제어는 다른 언어와 비교하여 최고로 알려져 있으며 가장 큰 이점을 얻습니다. 스마트 포인터가 다른 관리되는 언어와 유사한 메모리 오버 헤드를 생성한다는 사실은 언급하지 않았습니다. 자동 메모리 관리의 편의를 원한다면 Java 또는 C #에서 프로그래밍을 시작하지만 C / C ++에 그 혼란을 두지 마십시오.
luke1985

45
unique_ptr이 예의 @ lukasz1985 에는 성능 오버 헤드가 없습니다. 메모리를 포함한 리소스 관리는 다른 언어에 비해 C ++의 가장 큰 장점 중 하나입니다. 왜냐하면 제어력을 상실하지 않으면 서 성능 저하없이, 결정 론적으로 수행 할 수 있기 때문입니다. 어떤 사람들은 스마트 포인터를 통한 메모리 관리와 같이 C ++가 암시 적으로하는 것을 싫어하지만, 원하는 모든 것이 명백하게 명시 적이라면 C를 사용하십시오. 트레이드 오프는 문제가 훨씬 적습니다. 좋은 추천을 표하는 것이 불공평하다고 생각합니다.
TheCppZoo 2016 년

1
@ EdMaster : 분명히 트롤링했기 때문에 이전에 응답하지 않았습니다. 트롤에게 먹이를주지 마십시오.
Martin York

17
@LokiAstari 그는 트롤일지도 모른다. 그러나 그가 말하는 것은 사람들을 혼란스럽게 할 수도있다
TheCppZoo

1
@ 야우 : 예. 그러나 : boost::ptr_vector<>서브 클래스에 작업을 위임하는 대신 포인터를 소유한다는 것을 이해하므로 조금 더 효율적입니다. 그러나 주요 장점은 boost::ptr_vector<>참조가 아닌 포인터로 멤버를 노출하므로 표준 라이브러리의 알고리즘과 함께 사용하기가 쉽다는 것입니다.
Martin York

41

팩토리를 전혀 사용하지 않고 타입 시스템을 잘 활용하는 것에 대해 생각해 보셨습니까? 이런 종류의 일을하는 두 가지 다른 접근법을 생각할 수 있습니다.

옵션 1:

struct linear {
    linear(float x, float y) : x_(x), y_(y){}
    float x_;
    float y_;
};

struct polar {
    polar(float angle, float magnitude) : angle_(angle),  magnitude_(magnitude) {}
    float angle_;
    float magnitude_;
};


struct Vec2 {
    explicit Vec2(const linear &l) { /* ... */ }
    explicit Vec2(const polar &p) { /* ... */ }
};

다음과 같이 쓸 수 있습니다.

Vec2 v(linear(1.0, 2.0));

옵션 2 :

반복자와 함께 STL처럼 "태그"를 사용할 수 있습니다. 예를 들면 다음과 같습니다.

struct linear_coord_tag linear_coord {}; // declare type and a global
struct polar_coord_tag polar_coord {};

struct Vec2 {
    Vec2(float x, float y, const linear_coord_tag &) { /* ... */ }
    Vec2(float angle, float magnitude, const polar_coord_tag &) { /* ... */ }
};

이 두 번째 방법을 사용하면 다음과 같은 코드를 작성할 수 있습니다.

Vec2 v(1.0, 2.0, linear_coord);

각 생성자에 대해 고유 한 프로토 타입을 가질 수 있도록하는 동시에 멋지고 표현력도 뛰어납니다.


29

http://www.codeproject.com/Articles/363338/Factory-Pattern-in-Cplusplus 에서 매우 좋은 솔루션을 읽을 수 있습니다 .

가장 좋은 해결책은 "설명과 토론"에 있습니다. "정적 Create 메소드가 필요하지 않습니다"를 참조하십시오.

이 아이디어에서 나는 공장을 만들었다. Qt를 사용하고 있지만 std에 해당하는 QMap 및 QString을 변경할 수 있습니다.

#ifndef FACTORY_H
#define FACTORY_H

#include <QMap>
#include <QString>

template <typename T>
class Factory
{
public:
    template <typename TDerived>
    void registerType(QString name)
    {
        static_assert(std::is_base_of<T, TDerived>::value, "Factory::registerType doesn't accept this type because doesn't derive from base class");
        _createFuncs[name] = &createFunc<TDerived>;
    }

    T* create(QString name) {
        typename QMap<QString,PCreateFunc>::const_iterator it = _createFuncs.find(name);
        if (it != _createFuncs.end()) {
            return it.value()();
        }
        return nullptr;
    }

private:
    template <typename TDerived>
    static T* createFunc()
    {
        return new TDerived();
    }

    typedef T* (*PCreateFunc)();
    QMap<QString,PCreateFunc> _createFuncs;
};

#endif // FACTORY_H

샘플 사용법 :

Factory<BaseClass> f;
f.registerType<Descendant1>("Descendant1");
f.registerType<Descendant2>("Descendant2");
Descendant1* d1 = static_cast<Descendant1*>(f.create("Descendant1"));
Descendant2* d2 = static_cast<Descendant2*>(f.create("Descendant2"));
BaseClass *b1 = f.create("Descendant1");
BaseClass *b2 = f.create("Descendant2");

17

나는 대부분 받아 들인 대답에 동의하지만 기존 답변에서 다루지 않은 C ++ 11 옵션이 있습니다.

  • 값으로 팩토리 메소드 결과 리턴 하고
  • 저렴한 이동 생성자를 제공하십시오 .

예:

struct sandwich {
  // Factory methods.
  static sandwich ham();
  static sandwich spam();
  // Move constructor.
  sandwich(sandwich &&);
  // etc.
};

그런 다음 스택에 객체를 생성 할 수 있습니다.

sandwich mine{sandwich::ham()};

다른 것들의 하위 객체로 :

auto lunch = std::make_pair(sandwich::spam(), apple{});

또는 동적으로 할당 :

auto ptr = std::make_shared<sandwich>(sandwich::ham());

언제 사용할 수 있습니까?

공용 생성자에서 예비 계산없이 모든 클래스 멤버에 의미있는 이니셜 라이저를 제공 할 수없는 경우 해당 생성자를 정적 메소드로 변환 할 수 있습니다. 정적 메서드는 예비 계산을 수행 한 다음 멤버 별 초기화를 수행하는 개인 생성자를 통해 값 결과를 반환합니다.

필자 는 불필요하게 비효율적이지 않고 가장 명확한 코드를 제공하는 방법에 따라 ' might ' 라고 말합니다 .


1
OpenGL 리소스를 래핑 할 때 이것을 광범위하게 사용했습니다. 이동 의미를 강제로 삭제 한 복사 생성자와 복사 할당. 그런 다음 각 유형의 리소스를 생성하기위한 여러 정적 팩토리 메소드를 작성했습니다. 이것은 전달 된 열거 형에 따라 많은 중복 함수 매개 변수가있는 OpenGL의 열거 형 기반 디스패치보다 훨씬 읽기 쉽습니다. 매우 유용한 패턴입니다.이 답변이 높지 않다는 것에 놀랐습니다.
Fibbles

11

Loki는 팩토리 메소드추상 팩토리를 모두 가지고 있습니다 . 둘 다 Andei Alexandrescu에 의해 Modern C ++ Design 에 광범위하게 문서화되어있다 . 팩토리 메소드는 아마도 조금 달라 보이지만 (아마도 메모리가 제공되는 경우 팩토리가 해당 유형의 객체를 생성하기 전에 유형을 등록해야합니다.)


1
기한이 지났어도 (내가 논란의 여지가 있지만) 여전히 완벽하게 사용할 수 있습니다. 나는 여전히 새로운 C ++ 14 프로젝트에서 MC ++ D 기반의 Factory를 사용하여 효과를 발휘합니다! 또한 팩토리 및 싱글 톤 패턴은 아마도 가장 오래된 부품 일 것입니다. 같은 로키의 조각 동안 Function함께하고 유형 조작을 대체 할 수 std::function<type_traits>람다, 스레딩이를 rvalue 심판은 약간의 조정이 필요할 수 있습니다 의미를 가지고있는 동안 그가 그들 설명으로하고, 공장의 싱글에 대한 표준 교체는 없다.
metal

5

나는 그것이 너무 광범위하다고 생각하기 때문에 모든 질문에 대답하려고하지 않습니다. 몇 가지 메모 :

객체 생성이 다른 클래스로의 추출을 정당화하기에 충분히 복잡한 작업 인 경우가 있습니다.

그 클래스는 사실 팩토리가 아닌 빌더 입니다.

일반적인 경우에는 팩토리 사용자가 동적 할당으로 제한되도록하고 싶지 않습니다.

그런 다음 공장에서 스마트 포인터로 캡슐화 할 수 있습니다. 나는 이런 식으로 당신이 당신의 케이크를 먹고 그것을 먹을 수 있다고 생각합니다.

이는 또한 가치 별 반환과 관련된 문제를 제거합니다.

결론 : 객체를 반환하여 팩토리를 만드는 것은 실제로 (앞서 언급 한 2D 벡터와 같은) 일부 솔루션이지만 여전히 생성자를 대체하지는 않습니다.

과연. 모든 디자인 패턴에는 (언어 별) 제약 조건과 단점이 있습니다. 문제를 해결하는 데 도움이 될 때만 사용하는 것이 좋습니다.

"완벽한"공장 구현을하고 있다면 행운을 빕니다.


답변 해주셔서 감사합니다! 그러나 스마트 포인터를 사용하여 동적 할당 제한을 해제하는 방법을 설명 할 수 있습니까? 나는이 부분을 얻지 못했습니다.
Kos

@Kos, 스마트 포인터를 사용하면 사용자로부터 실제 개체의 할당 / 할당을 숨길 수 있습니다. 외부 세계에 정적으로 할당 된 객체처럼 동작하는 캡슐화 스마트 포인터 만 볼 수 있습니다.
Péter Török

@Kos, 엄격한 의미에서, AFAIR. 래핑 할 객체를 전달하면 어느 시점에서 동적으로 할당했을 수 있습니다. 그런 다음 스마트 포인터는 그 소유권을 가져와 더 이상 필요하지 않을 때 올바르게 제거됩니다 (시간은 다른 종류의 스마트 포인터에 따라 다르게 결정됨).
Péter Török

3

이것은 내 C ++ 11 스타일 솔루션입니다. 'base'매개 변수는 모든 하위 클래스의 기본 클래스입니다. 하위 클래스 인스턴스를 만드는 std :: function 객체 인 작성자는 하위 클래스의 정적 멤버 함수 'create (일부 인수)'에 대한 바인딩 일 수 있습니다. 이것은 아마도 완벽하지는 않지만 나를 위해 작동합니다. 그리고 그것은 일종의 '일반적인'솔루션입니다.

template <class base, class... params> class factory {
public:
  factory() {}
  factory(const factory &) = delete;
  factory &operator=(const factory &) = delete;

  auto create(const std::string name, params... args) {
    auto key = your_hash_func(name.c_str(), name.size());
    return std::move(create(key, args...));
  }

  auto create(key_t key, params... args) {
    std::unique_ptr<base> obj{creators_[key](args...)};
    return obj;
  }

  void register_creator(const std::string name,
                        std::function<base *(params...)> &&creator) {
    auto key = your_hash_func(name.c_str(), name.size());
    creators_[key] = std::move(creator);
  }

protected:
  std::unordered_map<key_t, std::function<base *(params...)>> creators_;
};

사용법에 대한 예.

class base {
public:
  base(int val) : val_(val) {}

  virtual ~base() { std::cout << "base destroyed\n"; }

protected:
  int val_ = 0;
};

class foo : public base {
public:
  foo(int val) : base(val) { std::cout << "foo " << val << " \n"; }

  static foo *create(int val) { return new foo(val); }

  virtual ~foo() { std::cout << "foo destroyed\n"; }
};

class bar : public base {
public:
  bar(int val) : base(val) { std::cout << "bar " << val << "\n"; }

  static bar *create(int val) { return new bar(val); }

  virtual ~bar() { std::cout << "bar destroyed\n"; }
};

int main() {
  common::factory<base, int> factory;

  auto foo_creator = std::bind(&foo::create, std::placeholders::_1);
  auto bar_creator = std::bind(&bar::create, std::placeholders::_1);

  factory.register_creator("foo", foo_creator);
  factory.register_creator("bar", bar_creator);

  {
    auto foo_obj = std::move(factory.create("foo", 80));
    foo_obj.reset();
  }

  {
    auto bar_obj = std::move(factory.create("bar", 90));
    bar_obj.reset();
  }
}

나에게 좋아 보인다. 정적 등록을 어떻게 구현합니까? 기본 클래스가 객체에 대한 서비스 클래스라고 상상해보십시오. 파생 클래스는 해당 객체에 특별한 종류의 서비스를 제공합니다. 또한 이러한 각 종류의 서비스에 대한 기반에서 파생 된 클래스를 추가하여 다른 종류의 서비스를 점진적으로 추가하려고합니다.
St0fF

2

공장 패턴

class Point
{
public:
  static Point Cartesian(double x, double y);
private:
};

그리고 컴파일러가 반환 값 최적화를 지원하지 않으면 버리지 마십시오. 아마도 많은 최적화가 포함되어 있지 않을 것입니다 ...


이것이 팩토리 패턴의 구현으로 간주 될 수 있습니까?
Dennis

1
@Dennis : 퇴보 한 사례로 생각합니다. 문제 Factory는 그것이 매우 일반적이며 많은 근거를 포함한다는 것입니다. 예를 들어 팩토리는 환경 / 설정에 따라 인수를 추가하거나 일부 캐싱 (Flyweight / Pools 관련)을 제공 할 수 있지만 이러한 상황은 일부 상황에서만 의미가 있습니다.
Matthieu M.

컴파일러를 바꾸는 것만 큼 쉽게 들리면 :)
rozina

@rozina : :) Linux에서 잘 작동합니다 (gcc / clang은 현저하게 호환됩니다). 64 비트 플랫폼 (정확히 기억한다면 특허가 적음)에서 더 좋아질 것이지만 Windows는 여전히 상대적으로 닫혀 있음을 인정합니다.
Matthieu M.

그리고 당신은 일부 subpar 컴파일러로 전체 임베디드 세계를 가지고 있습니다 .. :) 나는 반환 값 최적화가없는 것과 같은 것을 사용하고 있습니다. 그래도 좋겠다. 불행히도 현재 전환은 옵션이 아닙니다. 잘만되면 장래에 그것이 업데이트되거나 우리는 sth를 위해 전환 할 것입니다 :)
rozina

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.