C ++静态工厂构造函数

C++ static factory constructor

我正在进行模拟,它需要创建多个非常相似的模型。我的想法是创建一个名为model的类,并使用静态工厂方法来构造模型。例如:Model::CreateTriangle或Model::CreateFromFile。我从以前的Java代码中获取了这个想法,并在C++中寻找实现这一点的方法。

以下是我到目前为止的想法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#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;
}

关于这方面的一些问题:

  • 这是一种公认的、干净的制作物品的方法吗?
  • 返回的引用是否始终确保正确删除对象?
  • 没有指针有什么办法做到这一点吗?


就记录而言,这里是这个程序在适当的C++中的样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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
}

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


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

1
2
3
4
5
6
static Object createWithID(int id)
{
    Object obj;
    obj.id = id;
    return obj;
}

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


Is this an accepted and clean way of making objects?

很遗憾,这是可以接受的,但不干净。

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

这就是他们的目的。

Does the returned reference always ensure correct removal of the object?

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

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

最好返回一个智能指针。

但是正如前面提到的,放弃工厂功能的想法更好。

他们在这里完全没有必要。

Is there any way to do this without pointers?

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

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#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;
}


除非您使用多态性,否则工厂函数没有理由返回任何类型的指针,它们只能按值返回对象。任何现代编译器都会执行返回值优化,因此没有副本。

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

1
2
3
4
5
6
7
8
9
10
namespace Simulation {
  class Model {
   private:
    int id_;
   public:
    explicit Model(int id) : id_(id) {}

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
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类了。如果需要,您的创建函数甚至可以分布在多个文件中,或者成为构建器或工厂类的一部分。用法如下:

1
2
3
4
5
int main() {
  Simulation::Model m1(0);
  Simulation::Model m2 = Simulation::createTriangle(1);
  Simulation::Model m3 = Simulation::createSquare(2);
}

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

有两种可能的解决方法:

  • static Object createWithID(int id);将返回您创建的对象的副本,这样就足以在堆栈上使用

    1
    2
    Object tmp;
    tmp.id = id;
  • 使用C++ 11智能指针,让他们处理内存。

    1
    2
    3
    4
    5
    6
    7
    #include <memory>
    static std::unique_ptr<Object> createWithID(int id)
    {
        std::unique_ptr<Object> tmp(new Object());
        tmp->id = id;
        return std::move(tmp);
    }

  • 这是创建对象的绝对糟糕的方法。每次调用createWithID时,都会在无法销毁的免费商店上建造一个新的Object

    您应将createWithID改写为:

    1
    2
    3
    4
    5
    static Object createWithID(int id) {
        Object obj;
        obj.id = id;
        return obj;
    }

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

    如果要启用多态对象,应使用wheels::value_ptr之类的工具。