如何在C++中正确实现工厂方法模式[英] How to implement the factory method pattern in C++ correctly

本文是小编为大家收集整理的关于如何在C++中正确实现工厂方法模式的处理/解决方法,可以参考本文帮助大家快速定位并解决问题,中文翻译不准确的可切换到English标签页查看源文。

问题描述

C ++中有一件事,这使我很长一段时间都感到不舒服,因为老实说,即使听起来很简单,我也不知道该怎么做:

如何正确地在C ++中实现工厂方法?

目标:使客户可以使用出厂方法而不是对象的构造函数实例化某些对象,而不会产生不可接受的后果和性能命中.

通过"工厂方法模式",是指另一类或全局函数中定义的对象中的静态出厂方法.通常"将X类实例化的正常方式的概念重新定向到构造函数以外的任何地方".

让我浏览一些我想到的可能的答案.


0)不要制作工厂,制作构造函数.

这听起来不错(确实是最好的解决方案),但不是一般的补救措施.首先,在某些情况下,对象构造是一个足够的任务复合物,足以证明其提取到另一类的合理性.但是,即使将这个事实放在一边,即使对于仅使用构造函数的简单对象也经常也不会.

我知道的最简单示例是2-D矢量类.如此简单而棘手.我希望能够从笛卡尔和极地坐标中构建它.显然,我做不到:

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点描述.

.

另一种情况:尝试通过某些API的两个不透明的Typedef(例如无关域的GUID或GUID和一个Bitfield)超载,类型在语义上完全不同(因此 - 在理论上 - 有效的过载),但是哪个实际上,事实证明是同一件事 - 例如未签名的ints或无效指针.


1)Java Way

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)返回by-value

好,所以我们知道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中,具有保证的复制elision,不再可供应的代码 )

)

结论:通过返回对象制作工厂的确是某些情况的解决方案(例如前面提到的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());
    // ...
}

一个人可能认为它像魅力一样起作用.我们在代码中支付的唯一价格...

由于我已经写了所有这一切,并将其作为最后一个,所以我也必须不喜欢它. :)为什么?

首先...我真诚地不喜欢两相结构的概念,当我使用它时,我感到内gui.如果我用"如果存在,则处于有效状态"的断言来设计对象,我觉得我的代码更安全且易于错误.我喜欢这样.

必须删除该约定并改变我的对象的设计,只是为了制造工厂的目的.

我知道以上不会说服很多人,所以让我提出更多扎实的论点.使用两相构造,您不能:

  • 初始化const或参考成员变量,
  • 将参数传递给基类构造函数和成员对象构造函数.

,可能还有一些我现在无法想到的缺点,我什至没有特别义务,因为上述项目符号已经说服了我.

so:甚至不接近实施工厂的良好通用解决方案.


结论:

我们希望有一种对象实例化的方法,该方法将:

  • 允许均匀实例化,无论分配如何,
  • 给施工方法给出不同的,有意义的名称(因此不依赖于围位的过载),
  • 不要引入重大的性能命中,最好是一个重大的代码膨胀,尤其是在客户端,
  • 一般,如:可以为任何类引入.

我相信我已经证明了我提到的方式无法满足这些要求.

有提示吗?请为我提供解决方案,我不想认为这种语言不允许我正确实施如此微不足道的概念.

推荐答案

首先,在某些情况下 对象构造是一个任务综合体 足以证明其提取的合理性 另一堂课.

我相信这一点是不正确的.复杂性并不重要.相关性就是这样做的.如果可以在一个步骤中构造一个对象(不像构建器模式中的对象),则构造函数是正确的位置.如果您确实需要另一个类来执行工作,那么它应该是构造函数的辅助类别.

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

在其他情况下,工厂只是有助于解决诸如您提到的过载之类的小问题.如果可以以统一的方式使用它们,那就太好了,但是可能不可能是不可能的.

其他推荐答案

简单的工厂示例:

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

其他推荐答案

您是否考虑过根本不使用工厂,而是很好地使用了类型系统?我可以想到两种做这种事情的不同方法:

选项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);

这也是一个很好的表现力,同时允许您为每个构造函数拥有唯一的原型.

本文地址:https://www.itbaoku.cn/post/627458.html

问题描述

There's this one thing in C++ which has been making me feel uncomfortable for quite a long time, because I honestly don't know how to do it, even though it sounds simple:

How do I implement Factory Method in C++ correctly?

Goal: to make it possible to allow the client to instantiate some object using factory methods instead of the object's constructors, without unacceptable consequences and a performance hit.

By "Factory method pattern", I mean both static factory methods inside an object or methods defined in another class, or global functions. Just generally "the concept of redirecting the normal way of instantiation of class X to anywhere else than the constructor".

Let me skim through some possible answers which I have thought of.


0) Don't make factories, make constructors.

This sounds nice (and indeed often the best solution), but is not a general remedy. First of all, there are cases when object construction is a task complex enough to justify its extraction to another class. But even putting that fact aside, even for simple objects using just constructors often won't do.

The simplest example I know is a 2-D Vector class. So simple, yet tricky. I want to be able to construct it both from both Cartesian and polar coordinates. Obviously, I cannot do:

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

My natural way of thinking is then:

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

Which, instead of constructors, leads me to usage of static factory methods... which essentially means that I'm implementing the factory pattern, in some way ("the class becomes its own factory"). This looks nice (and would suit this particular case), but fails in some cases, which I'm going to describe in point 2. Do read on.

another case: trying to overload by two opaque typedefs of some API (such as GUIDs of unrelated domains, or a GUID and a bitfield), types semantically totally different (so - in theory - valid overloads) but which actually turn out to be the same thing - like unsigned ints or void pointers.


1) The Java Way

Java has it simple, as we only have dynamic-allocated objects. Making a factory is as trivial as:

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

In C++, this translates to:

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

Cool? Often, indeed. But then- this forces the user to only use dynamic allocation. Static allocation is what makes C++ complex, but is also what often makes it powerful. Also, I believe that there exist some targets (keyword: embedded) which don't allow for dynamic allocation. And that doesn't imply that the users of those platforms like to write clean OOP.

Anyway, philosophy aside: In the general case, I don't want to force the users of the factory to be restrained to dynamic allocation.


2) Return-by-value

OK, so we know that 1) is cool when we want dynamic allocation. Why won't we add static allocation on top of that?

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

What? We can't overload by the return type? Oh, of course we can't. So let's change the method names to reflect that. And yes, I've written the invalid code example above just to stress how much I dislike the need to change the method name, for example because we cannot implement a language-agnostic factory design properly now, since we have to change names - and every user of this code will need to remember that difference of the implementation from the specification.

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

OK... there we have it. It's ugly, as we need to change the method name. It's imperfect, since we need to write the same code twice. But once done, it works. Right?

Well, usually. But sometimes it does not. When creating Foo, we actually depend on the compiler to do the return value optimisation for us, because the C++ standard is benevolent enough for the compiler vendors not to specify when will the object created in-place and when will it be copied when returning a temporary object by value in C++. So if Foo is expensive to copy, this approach is risky.

And what if Foo is not copiable at all? Well, doh. (Note that in C++17 with guaranteed copy elision, not-being-copiable is no problem anymore for the code above)

Conclusion: Making a factory by returning an object is indeed a solution for some cases (such as the 2-D vector previously mentioned), but still not a general replacement for constructors.


3) Two-phase construction

Another thing that someone would probably come up with is separating the issue of object allocation and its initialisation. This usually results in code like this:

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());
    // ...
}

One may think it works like a charm. The only price we pay for in our code...

Since I've written all of this and left this as the last, I must dislike it too. :) Why?

First of all... I sincerely dislike the concept of two-phase construction and I feel guilty when I use it. If I design my objects with the assertion that "if it exists, it is in valid state", I feel that my code is safer and less error-prone. I like it that way.

Having to drop that convention AND changing the design of my object just for the purpose of making factory of it is.. well, unwieldy.

I know that the above won't convince many people, so let's me give some more solid arguments. Using two-phase construction, you cannot:

  • initialise const or reference member variables,
  • pass arguments to base class constructors and member object constructors.

And probably there could be some more drawbacks which I can't think of right now, and I don't even feel particularly obliged to since the above bullet points convince me already.

So: not even close to a good general solution for implementing a factory.


Conclusions:

We want to have a way of object instantiation which would:

  • allow for uniform instantiation regardless of allocation,
  • give different, meaningful names to construction methods (thus not relying on by-argument overloading),
  • not introduce a significant performance hit and, preferably, a significant code bloat hit, especially at client side,
  • be general, as in: possible to be introduced for any class.

I believe I have proven that the ways I have mentioned don't fulfil those requirements.

Any hints? Please provide me with a solution, I don't want to think that this language won't allow me to properly implement such a trivial concept.

推荐答案

First of all, there are cases when object construction is a task complex enough to justify its extraction to another class.

I believe this point is incorrect. The complexity doesn't really matter. The relevance is what does. If an object can be constructed in one step (not like in the builder pattern), the constructor is the right place to do it. If you really need another class to perform the job, then it should be a helper class that is used from the constructor anyway.

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

There is an easy workaround for this:

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

The only disadvantage is that it looks a bit verbose:

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

But the good thing is that you can immediately see what coordinate type you're using, and at the same time you don't have to worry about copying. If you want copying, and it's expensive (as proven by profiling, of course), you may wish to use something like Qt's shared classes to avoid copying overhead.

As for the allocation type, the main reason to use the factory pattern is usually polymorphism. Constructors can't be virtual, and even if they could, it wouldn't make much sense. When using static or stack allocation, you can't create objects in a polymorphic way because the compiler needs to know the exact size. So it works only with pointers and references. And returning a reference from a factory doesn't work too, because while an object technically can be deleted by reference, it could be rather confusing and bug-prone, see Is the practice of returning a C++ reference variable, evil? for example. So pointers are the only thing that's left, and that includes smart pointers too. In other words, factories are most useful when used with dynamic allocation, so you can do things like this:

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

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

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

In other cases, factories just help to solve minor problems like those with overloads you have mentioned. It would be nice if it was possible to use them in a uniform way, but it doesn't hurt much that it is probably impossible.

其他推荐答案

Simple Factory Example:

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

其他推荐答案

Have you thought about not using a factory at all, and instead making nice use of the type system? I can think of two different approaches which do this sort of thing:

Option 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) { /* ... */ }
};

Which lets you write things like:

Vec2 v(linear(1.0, 2.0));

Option 2:

you can use "tags" like the STL does with iterators and such. For example:

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 &) { /* ... */ }
};

This second approach lets you write code which looks like this:

Vec2 v(1.0, 2.0, linear_coord);

which is also nice and expressive while allowing you to have unique prototypes for each constructor.