如何最好地处理 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_WIDTH
和 DEFAULT_HEIGHT
吗? 或者我应该这样做吗?
// GameEngine.hpp
class GameEngine {
static const int DEFAULT_WIDTH = 800;
static const int DEFAULT_HEIGHT = 600;
Window window(800,600);
};
但这似乎很糟糕,因为我读到你不应该在 header 中做任何初始化,只在声明中,所以 DEFAULT_WIDTH
和 DEFAULT_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_ptr
或 shared_ptr
视情况而定)
(此外,如果您认为 class 几乎总是需要与指针一起使用,那么在指针周围制作一个包装器 class 就像 "plain object" 即使它是用内部指针实现的)
我想知道关于 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_WIDTH
和 DEFAULT_HEIGHT
吗? 或者我应该这样做吗?
// GameEngine.hpp
class GameEngine {
static const int DEFAULT_WIDTH = 800;
static const int DEFAULT_HEIGHT = 600;
Window window(800,600);
};
但这似乎很糟糕,因为我读到你不应该在 header 中做任何初始化,只在声明中,所以 DEFAULT_WIDTH
和 DEFAULT_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_ptr
或 shared_ptr
视情况而定)
(此外,如果您认为 class 几乎总是需要与指针一起使用,那么在指针周围制作一个包装器 class 就像 "plain object" 即使它是用内部指针实现的)