如何使用复杂的构造函数分配对象?

时间:2012-06-23 12:07:13

标签: c++ memory-management raii

我认为我对C ++的了解相当不错,我正在考虑实施比“玩具”程序更大的东西。我知道堆栈和堆内存与RAII-idiom之间的区别。

假设我有一个简单的类 point

class point {
public:
    int x;
    int y;
    point(int x, int y) : x(x), y(y) {}
};

我会在堆栈上分配点,因为对象很小。因为在64位计算机sizeof(point) == sizeof(void*)上,如果没有错,我会更进一步,默认情况下按值传递点数。

现在让我们假设一个更复杂的类战场,我想在类游戏中使用

class battlefield {
public:
    battlefield(int w, int h, int start_x, int start_y, istream &in) {
        // Complex generation of a battlefield from a file/network stream/whatever.
    }
};

由于我非常喜欢RAII并且在对象离开范围时自动清理,我很想在战场上分配战场。

game::game(const settings &s) :
        battlefield(s.read("w"), s.read("h"), gen_random_int(), gen_random_int(), gen_istream(s.read("level_number"))) {
    // ...
}

但我现在有几个问题:

  • 由于这个类没有零args构造函数,我必须在我使用战场的类的初始化列表中初始化它。这很麻烦,因为我需要来自某个地方的istream。这导致了下一个问题。

  • 复杂的构造者在某些时候“滚雪球”。当我在游戏类中使用 battlefield 并在初始化列表中将其初始化为游戏的构造函数时,游戏的构造函数也将变得相当复杂而游戏本身的初始化也可能变得很麻烦。 (当我决定将 istream 作为游戏构造函数的参数时)

  • 我需要辅助功能来填写复杂的参数。

我看到了这个问题的两个解决方案:

  • 要么为战场创建一个不初始化对象的简单构造函数。但是这种方法存在的问题是我有一个半初始化的对象,也就是违反RAII习语的对象。在这样的对象上调用方法时可能会发生奇怪的事情。

    game::game(const settings &s) {
        random_gen r;
        int x = r.random_int();
        int y = r.random_int();
        ifstream in(s.read("level_number"));
        in.open();
        this->battlefield.init(s.read("w"), s.read("h"), x, y, in);
        // ...
    } 
    
  • 或者我在游戏构造函数中的堆上分配 battlefield 。但我必须提防构造函数中的异常,我必须注意析构函数删除战场

    game::game(const settings &s) {
        random_gen r;
        int x = r.random_int();
        int y = r.random_int();
        ifstream in(s.read("level_number"));
        in.open();
        this->battlefield = new battlefield(s.read("w"), s.read("h"), x, y, in);
        // ...
    } 
    

我希望你能看到我正在考虑的问题。我遇到的一些问题是:

  • 我不知道这种情况的设计模式吗?

  • 大型C ++项目的最佳实践是什么?哪些对象在堆上分配,哪些对象在堆栈上分配?为什么呢?

  • 关于构造函数复杂性的一般建议是什么?从构造函数中读取文件太多了吗? (因为这个问题主要来自复杂的构造函数。)

2 个答案:

答案 0 :(得分:2)

  

但是这种方法存在的问题是我有一个半初始化的对象,也就是违反RAII惯用语的对象。

那不是 RAII 。概念是您使用对象来管理资源。当您获取堆内存,信号量,文件句柄等资源时,必须将所有权转移到资源管理类。这就是C ++中的智能指针。如果您想拥有对象的唯一所有权,则必须使用unique_ptr;如果您希望多个指针拥有所有权,则必须使用shared_ptr

  

或者我在游戏构造函数中在堆上分配战场。但我必须提防构造函数中的异常,我必须注意析构函数删除战场。

如果构造函数抛出异常,则不会调用该对象的析构函数,并且最终可能会出现在半熟对象中。在这种情况下,您必须记住在抛出异常之前在构造函数中执行的分配并释放所有这些分配。再次智能指针将有助于自动清理资源。见faq

  

哪些对象在堆上分配,哪些对象在堆栈上分配?为什么呢?

尽可能尝试在堆栈中分配对象。然后,您的对象仅在该块的范围内具有生命。如果你有一个不可能的情况,那就去堆分配 - 例如:你只知道运行时的大小,对象的大小太大而不能放在堆栈上。

答案 1 :(得分:2)

你可以让你的战场从设置中构建出来:

explicit battlefield(const settings& s);

或者,为什么不为battlefield创建工厂函数?

E.g。

battlefield CreateBattlefield(const settings& s)
{
    int w = s.read("w");
    int h = s.read("w");
    std::istream& in = s.genistream();
    return battlefield(w, h, gen_random_int(), gen_random_int(), in);
}

game::game(const settings &s) :
    battlefield(CreateBattlefield(s)) {
    // ...
}