C++静态工厂构造函数

3

我正在制作一个模拟项目,它需要创建多个相似的模型。我的想法是使用一个名为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;
}

关于这个问题有一些疑问:

  • 这是制作对象的可接受和干净的方式吗?
  • 返回的引用是否总是确保正确地移除对象?
  • 有没有不使用指针的方法来实现这一点?

3
由于你来自Java,耐心听我说:在C++中,如果你正在使用指针,那么你做错了;如果你正在使用new,那么你也做错了。请注意修改代码。 - Kerrek SB
相关:https://dev59.com/Ieo6XIcBkEYKwwoYTSwu - masoud
指针可能是可以的,但它们绝对不能拥有资源(除了非常低级别的组件)。 - Mankarse
1
@Mankarse:初步估计 :-) 更完整的规则应该是“不要有裸露的公共指针”。我认为 KNR 更喜欢说“指针不能拥有资源”,但这使得解释链表或树节点的工作变得更加困难。 - Kerrek SB
@KerrekSB:嗯,"KNR"是谁? - Cheers and hth. - Alf
显示剩余2条评论
6个回答

9

仅供参考,这个程序在标准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:完美回答了问题并提供了有用的额外信息。 - Mankarse
1
问题的代码同样是“适当的C ++”。两个代码示例都不是适当的设计(在答案的代码中,具有由工厂函数公开的私有构造函数只会增加复杂性、冗长和可能的低效率)。不幸的是,不清楚OP的代码是用来做什么的。 - Cheers and hth. - Alf
@Cheersandhth.-Alf:听取您的建议…尽管在没有OP提供更多信息的情况下,能够做的只有那么多。 - Mankarse

8
您的代码目前存在内存泄漏:任何使用new创建的对象都必须使用delete进行清理。最好不要在createWithID方法中使用new,代码如下:
static Object createWithID(int id) 
{
    Object obj;
    obj.id = id;
    return obj; 
}

这似乎需要额外复制对象,但实际上返回值优化通常会导致这个复制被优化掉。

没有理由使用工厂函数,而不是使用构造函数。构造函数的设计目的就是用来构建对象的,为什么不直接使用它们呢? - Cheers and hth. - Alf
5
我不同意这个观点,构造函数有一个局限性:它们在处理大量可选参数时不易扩展。此外,构造函数不能明确表示正在创建什么,而名为createFromFile的函数直接告诉你正在发生什么。 - Wilco
@Wilco:关于“构造方法不意味着正在创建什么”的说法,使用构造方法时,类型就在那里提到了,所以这句话纯属胡言乱语。但还是感谢您提出这些观点。 - Cheers and hth. - Alf
11
我认为Wilco想要表达的是,构造函数的名称并不意味着如何创建类,而是意味着创建的是什么类。相比于多个构造函数或者带有多个可选参数的构造函数,你可以使用多个具有有意义名称和较少可选参数或者没有意义的布尔参数的工厂方法来替代。工厂方法有其优缺点,但它们显然不是“荒谬”或“疯狂”的。 - Chris Drew

5

这是制作对象的一种被认可的方法吗?

很遗憾,它被认为是可以接受的,但不够干净。

而不是使用工厂函数,只需使用构造函数。

那就是它们的用途。

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

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

在您的示例中,引用显然误导了您,使您不能销毁动态分配的对象,而只是将其复制。

最好返回一个智能指针。

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

他们在这里完全不必要。

有没有不使用指针的方法来做到这一点?

如果“这”指的是动态分配,则没有,但您可以并且应该使用构造函数代替工厂函数。


例子:

#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说他们需要许多这样使类不同的函数。工厂函数是解决这个问题的一种方式。 - Chris Drew
@ChrisDrew:是的,工厂函数在某些情况下可能是合适的。一个主要的例子是std::make_shared。虽然改变设计可能是更好的解决方案(遗憾的是boost::intrusive_ptr没有进入C++11)。但仅仅做构造函数的工作并不是使用工厂函数的好选择。作为简单的构造函数替代品,它们具有负效用,增加了复杂性、冗长性、脆弱性、不必要的限制(对实例化和类派生都有限制)、临时命名和可能的低效率,没有任何优势。从技术上讲,这是一个不好的选择。 - Cheers and hth. - Alf
然而,C++ FAQ在某些情况下确实推荐使用工厂函数。主要是因为在人们(包括我)指出将单个析构函数保护起来更加清晰、更少的工作量(O(1)与O(n)相比),而不是将构造函数保护之前,这是comp.lang.c++中的共识。我不确定为什么它没有更新这个问题,我想我已经与马歇尔讨论过这个问题了。 - Cheers and hth. - Alf
很有可能,但您需要展示如何使用构造函数来回答OP的问题。您会如何实现“createTriangle()”,我猜还有“createSquare()”等函数? - Chris Drew
@Chris:你曾经认为模型可以动态地表示一个三角形。现在你又说捕捉这种知识静态地可能不是一个好主意。这没有道理。 - Cheers and hth. - Alf
显示剩余4条评论

2
通过调用 Object *obj = new Object();,您在堆上分配了内存。在该语句之后的行中,您返回对该对象的引用。到目前为止,一切正常,但是您从未删除创建的对象以实际释放内存。多次调用该函数会导致内存泄漏
有两种可能的解决方法:
  1. static Object createWithID(int id); would return a copy of the Object you create, so it would be enough to allocate it on the stack using

    Object tmp;
    tmp.id = id;
    
  2. use c++11 smart pointer to let them handle the memory.

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

1
这是一种非常糟糕的创建对象的方式。每次调用 createWithID,都会在自由存储器上构造一个新的 Object,该对象永远无法被销毁。
你应该改写 createWithID 为:
static Object createWithID(int id) {
    Object obj;
    obj.id = id;
    return obj; 
}

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

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


1

除非您正在使用多态,否则您的工厂函数没有理由返回任何类型的指针,它们可以通过值返回对象。任何现代编译器都会执行返回值优化,因此不会发生复制。

如果您想要一个“被接受和干净”的方法,那么这听起来相当基于观点,并且取决于该类将如何使用,但我会尽可能保持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类。 如果需要,您的创建函数甚至可以分布在多个文件中,或成为BuilderFactory类的一部分。 使用方式如下:
int main() {
  Simulation::Model m1(0);
  Simulation::Model m2 = Simulation::createTriangle(1);
  Simulation::Model m3 = Simulation::createSquare(2);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接