webdevqa.jp.net

ファクトリメソッドパターンをC ++で正しく実装する方法

C++には、これが1つあります。それは、長い間私を不快にさせてきました。

ファクトリメソッドを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で説明します。

もう1つのケース:あるAPIの2つの不透明なtypedef(関連のないドメインのGUID、またはGUIDとbitfieldなど)によってオーバーロードしようとすると、意味的にまったく異なる型 - 理論的には - 有効なオーバーロード)が、実際には同じことがわかります - 符号なし整数やvoidポインタのように


1)Javaのやり方

動的に割り当てられたオブジェクトしかないので、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++を複雑にするものですが、しばしば強力にするものでもあります。また、動的割り当てを許可しないターゲット(キーワード:embedded)もあると思います。そしてそれは、それらのプラットフォームのユーザーがクリーンな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);
    }
};

わかりました…それがあります。メソッド名を変更する必要があるので、醜いです。同じコードを2回書く必要があるので、それは不完全です。しかし、一度行われると、それはうまくいきます。右?

まあ、通常。しかし、時にはそうではありません。 Fooを作成するとき、私たちは実際には私たちのために戻り値の最適化をするためにコンパイラに頼っています、なぜならC++規格はコンパイラベンダーにとって十分に善意です。 C++の値による一時オブジェクト。そのため、Fooのコピーに費用がかかる場合、この方法は危険です。

そして、もしFooがまったくコピー可能ではないとしたら?ええ、ええ。 (コピーの排除が保証されているC++ 17では、上記のコードではコピー不可能であっても問題ありません

結論:オブジェクトを返すことでファクトリを作成することは、実際には(前述の2-Dベクトルなどの)解決策ですが、それでもコンストラクタの代わりにはなりません。


3)二相構造

誰かがおそらく思い付くだろうもう一つのことは、オブジェクト割り当てとその初期化の問題を切り離すことです。これは通常、次のようなコードになります。

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段階構成を使用すると、次のことができません。

  • constを初期化するか、メンバ変数を参照します。
  • 基本クラスのコンストラクタとメンバオブジェクトのコンストラクタに引数を渡す。

そして、おそらく今私が考えることができないいくつかのより多くの欠点があるかもしれません、そして私は特に私がすでに私を納得させるので私は特に強いられることを感じさえしません。

だから:工場を実装するための良い一般的な解決策にも近い。


結論:

次のようなオブジェクトのインスタンス化の方法が欲しいです。

  • 割り当てに関係なく、一様なインスタンス化を可能にします。
  • 作法に異なる意味のある名前をつける(つまり引数によるオーバーロードに頼らない)
  • 特にクライアント側で、著しいパフォーマンスの低下や、できれば重大なコードの膨大なヒットが発生しないようにします。
  • 以下のように一般的である:あらゆるクラスに導入することが可能。

私が述べた方法がそれらの要求を満たさないことを私は証明したと私は信じています。

何かヒントは?私に解決策を提供してください、私はこの言語が私がそのような些細な概念を適切に実装することを可能にしないとは思いません。

302
Kos

まず第一に、オブジェクト構築が他のクラスへの抽出を正当化するのに十分なほど複雑なタスクである場合があります。

この点は間違っていると思います。複雑さはそれほど重要ではありません。関連性は何ですか。オブジェクトを1つのステップで構築できる場合(Builderパターンの場合とは異なります)、コンストラクターはそれを実行するのに適した場所です。もしあなたが本当にその仕事を実行するために別のクラスが必要なら、それはとにかくコンストラクタから使われるヘルパークラスであるべきです。

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();

他の場合では、工場は単にあなたが言及した過負荷を伴うもののような小さな問題を解決するのを助けます。それを統一的に使用することが可能であれば、それはいいことですが、それがおそらく不可能であることはあまり害にはなりません。

97
Sergei Tachenov

簡単な工場の例:

// 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();
    }
};
45
Martin York

工場をまったく使わずに、代わりに型システムをうまく利用することを考えましたか。私はこのようなことをする2つの異なるアプローチを考えることができます:

オプション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 &) { /* ... */ }
};

この2番目の方法では、次のようなコードを書くことができます。

Vec2 v(1.0, 2.0, linear_coord);

それぞれのコンストラクタに固有のプロトタイプを使用できるようにしながら、これも素敵で表現力豊かです。

37
Evan Teran

あなたはで非常に良い解決策を読むことができます: http://www.codeproject.com/Articles/363338/Factory-Pattern-in-Cplusplus

最善の解決策は「コメントと議論」です。「静的なCreateメソッドは不要」を参照してください。

この考えから、私は工場を作りました。私はQtを使っていることに注意してください、しかしあなたはQMapとQStringをstdと同等のものに変えることができます。

#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");
26
mabg

私は主に受け入れられた答えに賛成しますが、既存の答えでカバーされていない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());

いつ使うの?

パブリックコンストラクタで、事前計算なしにすべてのクラスメンバに意味のあるイニシャライザを指定することが不可能な場合は、そのコンストラクタを静的メソッドに変換します。静的メソッドは予備計算を実行してから、メンバーごとの初期化を行うだけのプライベートコンストラクターを介して値resultを返します。

'may'と言うのは、不必要に非効率的であることなく、どちらのアプローチが最も明確なコードを与えるかに依存するからです。

15
mbrcknl

Lokiには、 Factory MethodAbstract Factory の両方があります。両方ともAndei AlexandrescuによるModern C++ Designに(広範囲に)文書化されています。ファクトリメソッドは、多少異なりますが、おそらくあなたが期待しているものに近いでしょう(少なくともメモリが機能する場合は、ファクトリがそのタイプのオブジェクトを作成する前にタイプを登録する必要があります)。

11
Jerry Coffin

私はそれが広すぎると思うので、私は私の質問すべてに答えることを試みない。ちょっとしたメモ:

オブジェクト構築が他のクラスへの抽出を正当化するのに十分に複雑なタスクである場合があります。

そのクラスは実際にはFactoryではなく Builder です。

一般的なケースでは、私はファクトリのユーザに動的な割り当てを強制させたくありません。

それからあなたはあなたの工場にスマートポインタでそれをカプセル化させることができました。私はこのようにあなたがあなたのケーキを持っていてもそれを食べることができると信じています。

これにより、値による戻りに​​関連する問題も排除されます。

結論:オブジェクトを返すことでファクトリを作成することは、実際には(前述の2-Dベクトルなどの)解決策ですが、それでもコンストラクタの代わりにはなりません。

確かに。すべてのデザインパターンには(言語固有の)制約と欠点があります。それらがあなたの問題を解決するのを助けるときにだけそれらを使用することをお勧めします、彼ら自身のためにではありません。

あなたが「完璧な」工場実装の後であれば、幸運にも。

5
Péter Török

工場パターン

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

そして、もしあなたのコンパイラが戻り値の最適化をサポートしていないのであれば、それを捨ててください。

2
Matthieu M.

これは私のC++ 11スタイルのソリューションです。パラメータ 'base'はすべてのサブクラスの基本クラス用です。作成者は、サブクラスのインスタンスを作成するためのstd :: functionオブジェクトで、サブクラスの 'static member function' create(some args)へのバインディングになるかもしれません。これは完璧ではないかもしれませんが、私にとってはうまくいきます。そしてそれはちょっと「一般的な」解決策です。

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();
  }
}
1
DAG

この質問は3年前に回答されていますが、これはあなたが探していたものかもしれません。

Googleは数週間前に、簡単で柔軟な動的オブジェクト割り当てを可能にするライブラリをリリースしました。ここにあります: http://google-opensource.blogspot.fr/2014/01/introducing-infact-library.html

1
Florian Richoux