C++ 静态工厂构造函数

Posted

技术标签:

【中文标题】C++ 静态工厂构造函数【英文标题】:C++ static factory constructor 【发布时间】:2014-07-13 17:48:44 【问题描述】:

我正在进行模拟,它需要创建多个相当相似的模型。我的想法是有一个名为 Model 的类并使用静态工厂方法来构造模型。例如; Model::createTriangleModel::createFromFile。我从以前的 java 代码中获得了这个想法,并正在寻找在 C++ 中实现它的方法。

这是我到目前为止的想法:

#include <iostream>

class Object 
    int id;

public:
    void print()  std::cout << id << std::endl; 

    static Object &createWithID(int id) 
        Object *obj = new Object();

        obj->id = id;

        return *obj; 
    
;

int main() 
    Object obj = Object::createWithID(3);

    obj.print();

    return 0;

关于这个的一些问题:

这是一种可接受且干净的制作对象的方式吗? 返回的引用是否始终确保正确移除对象? 没有指针有没有办法做到这一点?

【问题讨论】:

既然您来自 Java,请注意这一点:在 C++ 中,如果您使用指针,那么您做错了,如果您使用 new,那么您正在做错了。 相关:***.com/questions/5120768/… 指针可以,但绝对不能拥有资源(非常低级别的组件之外)。 @Mankarse:首先近似 :-) 更完整的规则是“没有裸露的公共指针”。我认为 KNR 更喜欢说“指针不能拥有资源”,但这使得解释链表或树节点如何工作变得有点困难。 @KerrekSB:嗯,谁是“KNR”? 【参考方案1】:

您的代码当前包含内存泄漏:使用new 创建的任何对象都必须使用delete 清理。 createWithID 方法最好不要使用 new,看起来像这样:

static Object createWithID(int id) 

    Object obj;
    obj.id = id;
    return obj; 

这似乎需要额外的对象副本,但实际上return value optimization 通常会导致该副本被优化掉。

【讨论】:

没有理由在构造函数的地方使用工厂函数。构造函数是为构造而设计的。为什么不使用它们。 我不同意这一点,构造函数有一个限制:它们不能很好地扩展到大量可选参数。此外,构造方法并不暗示正在创建什么,而名为 createFromFile 的函数会告诉您确切发生了什么。 @Wilco:re “另外,构造函数方法并不暗示正在创建什么”,构造函数在其中提到了类型,所以这句话纯粹是疯狂。但感谢您提出这些观点。 @Cheersandhth.-Alf 我认为 Wilco 试图说明的一点是构造函数的名称并不意味着 如何 创建一个类而不是 将创建什么 类。而不是多个构造函数或具有多个可选参数的构造函数,您可以拥有多个具有有意义名称和更少可选参数或无意义布尔参数的工厂方法。工厂方法有利有弊,但它们显然不是“荒谬”或“疯狂”。【参考方案2】:

为了记录,下面是这个程序在适当的 C++ 中的样子:

class Object

    int id;

    // private constructor, not for general use
    explicit Object(int i) : id(i)  

public:
    static Object createWithID(int id)
    
        return Object(id);
    
;

int main()

    Object obj1 = Object::createWithID(1);
    auto obj2 = Object::createWithID(2);   // DRY

    // return 0 is implied

这可能不是人们通常所说的“工厂”,因为工厂通常涉及一些动态类型选择。但是,有时使用术语“命名构造函数”来指代返回类实例的静态成员函数。

【讨论】:

+1:完美回答问题并提供有用的附加信息。 问题的代码也是“正确的 C++”。这两个代码示例都不是正确的设计(在答案的代码中,具有工厂函数公开的私有构造函数只会增加复杂性、冗长性和可能的​​低效率)。不幸的是,不清楚 OP 的代码是什么for @Cheersandhth.-Alf:要点...虽然只有这么多可以在没有 OP 提供更多信息的情况下完成。【参考方案3】:

这是一种可接受且干净的制作对象的方式吗?

它(不幸地)被接受了,但它不干净。

使用构造函数代替工厂函数。

这就是他们的目的。

返回的引用是否始终确保正确删除对象?

该引用无关紧要,除非它误导函数的用户。

在您的示例中,引用显然误导了您自己不破坏动态分配的对象,而只是复制它。

最好返回一个智能指针。

但如前所述,最好放弃工厂函数的想法。

这里完全没有必要。

有没有什么方法可以在没有指针的情况下做到这一点?

不,如果“this”指的是动态分配,则不是,但您可以并且应该使用构造函数而不是工厂函数。


例子:

#include <iostream>

namespace better 
    using std::ostream;

    class Object
    
    public:
        auto id() const -> int  return id_; 
        explicit Object( int const id): id_( id ) 
    private:
        int id_;
    ;

    auto operator<<( ostream& stream, Object const& o )
        -> ostream&
     return (stream << o.id()); 
  // namespace better

auto main()
    -> int

    using namespace std;
    cout << better::Object( 3 ) << endl;

【讨论】:

如果工厂函数是唯一的,那么工厂函数将是不必要的,但 OP 表示他们需要许多使类与众不同的函数。工厂函数是解决这个问题的一种方法。 @ChrisDrew:是的,工厂函数可能是合适的,在少数情况下。一个主要的例子是std::make_shared。虽然改变设计可能是一个更好的解决方案(很遗憾boost::intrusive_ptr 没有进入 C++11)。然而,仅仅做构造函数的工作并不是一个好的选择工厂函数的情况。作为简单的构造函数替换,它们具有负面效用,增加了复杂性、冗长、脆弱性、不必要的限制(对实例化和类派生)、临时命名以及可能效率低下,没有任何优势。从技术上讲,这是一个不好的选择。 但是,C++ FAQ 确实在某些情况下推荐了工厂函数。主要是因为这是 comp.lang.c++ 中的共识,在包括我在内的人们指出使单个析构函数受保护之前,与使构造函数受保护相比,O(1) 与 O(n) 更清洁且工作量更少。我不知道为什么没有对此进行更新,我想我确实与 Marshall 讨论过。 很有可能,但您需要展示如何使用构造函数来回答 OP 的问题。你会怎么做“createTriangle()”,我假设,“createSquare()”等等 @Chris:重新“createTriangle()”工厂函数,用类 Triangle 构造函数替换它。我已经说过很多次了。不必为每个能想到的新工厂函数名称重复它。【参考方案4】:

通过调用Object *obj = new Object();,您确实在堆上分配了内存。在该语句后面的行中,您确实返回了对该对象的引用。到目前为止,一切都很好,但您永远不会删除您创建的对象以实际释放内存。通过多次调用该函数,您将在内存泄漏中运行。

有两种可能的解决方法:

    static Object createWithID(int id); 将返回您创建的对象的副本,因此使用

    在堆栈上分配它就足够了
    Object tmp;
    tmp.id = id;
    

    使用 c++11 智能指针让它们处理内存。

    #include <memory>
    static std::unique_ptr<Object> createWithID(int id)
    
        std::unique_ptr<Object> tmp(new Object());
        tmp->id = id;
        return std::move(tmp);
    
    

【讨论】:

【参考方案5】:

这是一种绝对糟糕的创建对象的方式。每次调用createWithID,都会在永远无法销毁的免费存储上构建一个新的Object

您应该将createWithID 重写为:

static Object createWithID(int id) 
    Object obj;
    obj.id = id;
    return obj; 

或者更好的是,您可以只为您的 Object 对象提供一个构造函数。

如果你想启用多态对象,你应该使用wheels::value_ptr之类的东西。

【讨论】:

【参考方案6】:

除非您使用多态性,否则您的工厂函数没有理由返回任何类型的指针,它们可以按值返回对象。任何现代编译器都会做return value optimization,所以没有副本。

如果你追求的是一种“被接受和干净”的方式,那么这听起来很基于意见,并且取决于这个类的使用方式,但我要做的是将Model 的定义尽可能小。仅包含正常使用所需的最少构造函数来完成其工作所需的内容:

namespace Simulation 
  class Model 
   private: 
    int id_;
   public:
    explicit Model(int id) : id_(id) 

    // minimum required to do the job...
  ;

然后,我将定义函数以分别创建各种风格的Model。例如,作为命名空间中的非成员、非友元函数:

namespace Simulation   
  Model createTriangle(int id) 
    Model model(id);
    // do whatever you need to do to make it a triangle...
    return model;
  

  Model createSquare(int id) 
    Model model(id);
    // do whatever you need to do to make it a square...
    return model;
    

这样,如果您发现需要另一种风格的 Model,则无需更改 Model 类。如果需要,您的创建函数甚至可以分布在多个文件中,或者成为 Builder 或 Factory 类的一部分。用法如下:

int main() 
  Simulation::Model m1(0);
  Simulation::Model m2 = Simulation::createTriangle(1);
  Simulation::Model m3 = Simulation::createSquare(2);

【讨论】:

以上是关于C++ 静态工厂构造函数的主要内容,如果未能解决你的问题,请参考以下文章

使用受保护构造函数和复制构造函数创建 C++ 非堆工厂对象

考虑使用静态工厂方法替代构造函数

C++ 模板工厂构造函数/反序列化

Effective Java1静态工厂的方式代替构造函数

如何在构造函数 C++ 中使用静态变量?

EffectiveJava