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

时间:2015-06-03 21:54:58

标签: c++ pointers object initialization declaration

我想知道对象必须具有相对较大的范围/长寿命的对象,对象初始化和存储的最佳方法是什么。我们假设我们有一个GameEngine类需要初始化并保留对Window的引用以进行渲染。整个程序的生命周期都需要参考,窗口至少需要知道它的尺寸。

在Java中,我这样做:

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

我理解在C ++中,第一个已经调用了Window类的默认构造函数,因此是声明初始化。因此,window = Window(width, height);赋值,从而丢弃已存在的对象。

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

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

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

但话又说回来,我经常读到一个人应该尽可能地支持普通对象而不是指针,事实上,我很快就陷入了一堆乱七八糟的指针,所以我正在寻找另一种方式。

另一个解决方案似乎是设计你的对象以获得一个没有参数的构造函数,并在以后设置该对象

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

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

这有效,但有一个严重的缺点:对象(至少在这种情况下)可能处于不一致状态,因为试图在没有设置宽度/高度的情况下显示窗口会导致错误或崩溃。它确实适用于某些对象,但对于大多数它没有。

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

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

或者甚至喜欢

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

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

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

但这看起来很糟糕,因为我已经读过你不应该在标题中进行任何初始化,只能声明,所以DEFAULT_WIDTHDEFAULT_HEIGHT的值实际上不应该在这一点(并且只在.cpp中初始化,对吗?)。

我错过了一个选项吗?或者在C ++中常见的是假设程序员应该知道他在做什么并在使用它们之前将其对象保持在一致状态? 何时使用哪种方法?

5 个答案:

答案 0 :(得分:4)

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

HPP

class Game
{
    private:
        Window window_;

    public:
        Game(int, int);
}

CPP

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

这将在构造Game对象时构造窗口对象,并且它将持续存在直到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对象被销毁时自动删除窗口,并在每次调用std :: make_unique构建新窗口时自动删除窗口。这是关于unique_ptr的一些文档: http://en.cppreference.com/w/cpp/memory/unique_ptr

答案 1 :(得分:1)

你显然误解了C ++。你永远不会在标题中拥有Window window;。这定义了一个Window对象,每次包含标题时

您可能拥有class GameEngine { Window window; .... },但实际上根本没有创建窗口。每个GameEngine构造函数都有一个初始化列表,在那里你初始化window。有道理:游戏引擎创建了它所需的窗口。

答案 2 :(得分:1)

如果您正在谈论类成员,那么声明与构造函数被调用的点相同。这些成员的初始化正是初始化程序列表(您似乎知道的)是!

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

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

然后你可以从游戏构造函数中调用窗口类的构造函数,如下所示:

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

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

声明:

extern Window window;

实现:

Window window(DEFAULT_WIDTH, DEFAULT_HEIGHT);

答案 3 :(得分:1)

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

但这并不总是可行的(例如,如果你想要一个在游戏中某个特定事件发生之前不会创建的窗口);或者作为一个新的程序员可能很难把头包起来。

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

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

std::unique_ptr<Window> p_window;

然后,当您准备好创建窗口时,您可以执行代码:

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

智能指针在其包含对象被销毁时负责调用delete,如果你不小心尝试做一个&#34;浅拷贝&#34;它会给出编译错误。

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

答案 4 :(得分:0)

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

如果创建对象的时间确实不确定,或者在准备好创建有效对象之前确实需要为它声明变量,那么指针就可以了,这是正确的。

您提到的建议的重点不是改变您设计对象的方式,而是您需要重新考虑如何使用对象:您需要忘记从一切事物中汲取的习惯-is-a-pointer编程环境,如Java。

(尽管您应该使用智能指针,例如unique_ptrshared_ptr

(另外,如果你认为一个类几乎总是需要与指针一起使用,那么在指针周围创建一个类似“普通对象”的包装类是很有用的,即使它是用指针​​内部实现的)