如何最好地处理 C++ object 初始化:空构造函数或指针?

How to best handle C++ object initialization: empty constructors or pointers?

我想知道关于 object 的 object 初始化和存储 的最佳方法是什么大范围/长寿命。假设我们有一个 GameEngine class 需要初始化并保存对 Window 的引用以进行渲染。在程序的整个生命周期中都需要引用,并且 window 至少需要知道它的维度。

在 Java 中,我会这样做:

// Declaration:
Window window;
// Initialization:
window = new Window(width, height);

我知道在 C++ 中,第一个已经调用了 Window class 的默认构造函数,因此是 声明 初始化。因此,拥有 window = Window(width, height); 将是 分配 ,丢弃已经存在的 object.

我能找到的第一个解决方案是使用指针:

// GameEngine.hpp
class GameEngine {
    Window *window;
};

// Somewhere in GameEngine.cpp:
window = new Window(width, height);

但话又说回来,我经常阅读 should favor plain objects over pointers when possible,事实上,我很快就陷入了指针的混乱之中,所以我正在寻找另一种方式。

另一种解决方案似乎是将您的 object 设计为具有不带参数的构造函数,并稍后在 :

上设置 object
// GameEngine.hpp
class GameEngine {
    Window window;
};

// Somewhere in GameEngine.cpp
window.setWidth(width);
window.setHeight(height);

这行得通,但有一个严重的缺点:object(至少在这种情况下)可能处于不一致状态,因为试图在不设置 [=70= 的情况下显示 window ] 会导致错误或崩溃。它对某些 object 有效,但对大多数无效。

避免这种情况的一种方法是使用默认值。例如,Window class 的构造函数可能如下所示:

Window::Window(int width = 800, int height = 600) {}

甚至这样:

Window::Window() : width(DEFAULT_WIDTH), height(DEFAULT_HEIGHT) {}

但在很多情况下,默认值很难确定。另外,他们应该从哪里来? Window class 应该定义 DEFAULT_WIDTHDEFAULT_HEIGHT 吗? 或者我应该这样做吗?

// GameEngine.hpp
class GameEngine {
    static const int DEFAULT_WIDTH = 800;
    static const int DEFAULT_HEIGHT = 600;
    Window window(800,600);
};

但这似乎很糟糕,因为我读到你不应该在 header 中做任何初始化,只在声明中,所以 DEFAULT_WIDTHDEFAULT_HEIGHT 的值不应该实际上此时已知(并且仅在 .cpp 中初始化,对吗?)。

我是否遗漏了一个选项? 或者在 C++ 中是否普遍认为程序员应该知道他在做什么并注意让他的 object 进入使用它们之前的一致状态? 什么时候使用哪种方法?

您显然误解了 C++。你永远不会像 header 那样拥有 Window window;。这定义了一个 Window object, 每次包含 header !

您可能有 class GameEngine { Window window; .... },但实际上根本没有创建 window。每个 GameEngine 构造函数都有一个初始化列表,您可以在其中初始化 window。有道理:游戏引擎创建它需要的 window。

如果您谈论的是 class 成员,那么声明是 而不是 调用构造函数的同一点。这些成员的初始化正是初始化列表(您似乎知道)的用途!

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

class Game {
 Window window;
public:
 Game();
};

然后你可以像这样从游戏构造函数中调用 window class 的构造函数:

Game::Game() : window(DEFAULT_HEIGHT, DEFAULT_WIDTH) {}

如果你在谈论全局变量:如果你真的需要一个全局变量 object(虽然你可能不想要那个)你可以(并且应该!)用外部声明 object header 中的链接(这只会使名称可用,但不会调用任何构造函数)并在实现中进行定义:

声明:

extern Window window;

实施:

Window window(DEFAULT_WIDTH, DEFAULT_HEIGHT);

理想情况下,您会设计 classes,以便您需要的所有初始化都可以在构造函数中进行。 Example here.

但这并不总是可行的(例如,如果您想要 Window 在游戏期间发生某些特定事件之前不会创建);或者作为一个新程序员可能很难全神贯注。


一种方法是使用指针 - 但使用智能指针而不是原始指针。

如果您的 class 需要包含一些对象句柄,但您还没有准备好创建对象,那么您可以有一个 class 成员:

std::unique_ptr<Window> p_window;

然后当你准备创建window时,你可以执行代码:

p_window.reset( new Window(bla bla bla) );

智能指针负责在其包含的对象被销毁时调用delete,如果您不小心尝试执行"shallow copy".

,它将给出编译错误

要在指针指向某处后使用它,您可以写 p_window->bla...,并检查它是否已分配,您可以使用 if ( p_window ).

如果你只想构造一次并且可以在 class 的初始化中完成,那么你不需要指针。您可以将其声明为成员并在构造函数中对其进行初始化,如下所示:

HPP

class Game
{
    private:
        Window window_;

    public:
        Game(int, int);
}

CPP

Game::Game(int width, int height) : window_(width, height)
{
}

这将在您构造 Game 对象时构造 window 对象,并且它将持续存在直到 Game 对象被销毁。如果您希望以后能够构建它或随时重建它,请像这样使用 std::unique_ptr:

HPP

class Game
{
    private:
       std::unique_ptr<Window> window_;

    public:
        Game(int, int);
        void SomeMethod(int, int);

}

CPP

Game::Game(int width, int height)
{
    window_ = std::make_unique<Window>(width, height);
}

Game::SomeMethod(int width, int height)
{
    window_ = std::make_unique<Window>(width, height);
}

这将在 Game 对象被销毁时自动删除 window,并在您每次调用 std::make_unique 构建新对象时自动删除 window。这是 unique_ptr 上的一些文档: http://en.cppreference.com/w/cpp/memory/unique_ptr

您缺少的选项是在创建对象时初始化 Window 对象。在知道如何初始化对象之前,不要在函数中声明 Window 个对象。如果您有一个带有 Window 成员的对象,请让该对象的构造函数初始化 Window 成员。

如果对象的创建时间确实不确定,或者您确实需要在准备创建有效对象之前为它声明一个变量,那么指针就很好并且是正确的做法。

你提到的建议的重点不是改变你设计对象的方式,而是你需要重新考虑你如何使用这些对象:你需要忘掉你的习惯我们从像 Java 这样的一切都是指针的编程环境中学到了东西。

(尽管您应该使用智能指针,如 unique_ptrshared_ptr 视情况而定)

(此外,如果您认为 class 几乎总是需要与指针一起使用,那么在指针周围制作一个包装器 class 就像 "plain object" 即使它是用内部指针实现的)