Reputation: 4681
I'm wondering what is the best way to go about object initialization and storage with regards to objects that have to have a relatively large scope / long lifetime. Let's say we have a GameEngine
class that needs to initialize and hold a reference to a Window
for rendering. The reference is needed throughout the program's lifetime and the window needs to know its dimensions, at least.
In Java, I'd do it like this:
// Declaration:
Window window;
// Initialization:
window = new Window(width, height);
I understood that in C++, the first would already call the default constructor of the Window class, hence be declaration and initialization. Having a window = Window(width, height);
would therefore be assignment, throwing away the already existing object.
The first solution I could find was to use a pointer:
// GameEngine.hpp
class GameEngine {
Window *window;
};
// Somewhere in GameEngine.cpp:
window = new Window(width, height);
But then again, I constantly read one should favor plain objects over pointers whenever possible and in fact, I got myself into a mess of pointers in no time, so I am looking for another way.
Another solution seems to design your objects to have a constructor without parameters and set up the object later on:
// GameEngine.hpp
class GameEngine {
Window window;
};
// Somewhere in GameEngine.cpp
window.setWidth(width);
window.setHeight(height);
This works, but has a serious drawback: the object (at least in this case) could be in an inconsistent state, as trying to display the window without setting width/height would result in an error or crash. It does work for some objects, but for most it does not.
One way to avoid this would be to have default values. For example, the constructor for the Window class could look like this:
Window::Window(int width = 800, int height = 600) {}
Or even like that:
Window::Window() : width(DEFAULT_WIDTH), height(DEFAULT_HEIGHT) {}
But in many cases, default values will be hard to determine. Also, where should they be coming from? Should the Window class define DEFAULT_WIDTH
and DEFAULT_HEIGHT
? Or should I even do this?
// GameEngine.hpp
class GameEngine {
static const int DEFAULT_WIDTH = 800;
static const int DEFAULT_HEIGHT = 600;
Window window(800,600);
};
But that seems bad, as I've read that you should not do any initialization in the header, only declaration, so the values of DEFAULT_WIDTH
and DEFAULT_HEIGHT
should not actually be known at this point (and only be initialized in the .cpp, correct?).
Am I missing an option? Or is it common in C++ to assume that the programmer should know what he's doing and take care of getting his objects in a consistent state before using them? When to use which approach?
Upvotes: 8
Views: 16696
Reputation:
The option you're missing is to initialize Window
objects when they're created. Don't declare Window
objects in your functions before you know how to initialize them. If you have an object with a Window
members, have the constructor to the object initialize the Window
member.
Pointers are fine and the right thing to do if the time of creation of an object really is indeterminate, or you really and truly need to declare a variable for it before you are ready to create a valid object.
The point of the advice you mention is not to change how you design objects, but that you need to reconsider how you use the objects: you need to unlearn habits you've picked up from everything-is-a-pointer programming environments like Java.
(although you should use smart pointers, like unique_ptr
or shared_ptr
as appropriate)
(also, if you believe a class will pretty much always need to be used with pointers, it is useful to make a wrapper class around the pointer that acts like a "plain object" even though its implemented with a pointer inside)
Upvotes: 0
Reputation: 369
If you want to construct it only once and it can be done in the initialization of the class then you dont need a pointer. You can declare it as a member and initialize it in the constructor like so:
HPP
class Game
{
private:
Window window_;
public:
Game(int, int);
}
CPP
Game::Game(int width, int height) : window_(width, height)
{
}
This will construct the window object when you construct the Game object and it will persist until the Game object is destroyed. If you want to be able to construct it later or reconstruct it at any time then use a std::unique_ptr like so:
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);
}
This will automatically delete the window when the Game object is destroyed and automatically delete the window each time you call std::make_unique to build a new one. Here is some doc on unique_ptr: http://en.cppreference.com/w/cpp/memory/unique_ptr
Upvotes: 5
Reputation: 141618
Ideally, you would design your classes so that all the initialization you need can occur in the constructor. Example here.
But this isn't always possible (e.g. if you want a Window that isn't created until some particular event happens during the Game); or it can be hard to wrap your head around as a new programmer.
One approach is to use pointers - but use a smart pointer instead of a raw pointer.
If your class needs to contain some object handles but you aren't ready to create the object yet, then you can have a class member:
std::unique_ptr<Window> p_window;
Then when you are ready to create the window, you can execute the code:
p_window.reset( new Window(bla bla bla) );
The smart pointer takes care of calling delete
when its containing object is destroyed, and it will give a compile error if you accidentally try to do a "shallow copy".
To use the pointer once it is pointing somewhere you would write p_window->bla...
, and to check if it has been assigned yet you can use if ( p_window )
.
Upvotes: 1
Reputation: 15683
If you're talking about class members, then declaration is not the same point the constructor is being called. Initialization of such members is exactly what initializer lists (which you do seem to know about) are for!
class Window {
int x;
int y;
public:
Window(int x, int y);
};
and
class Game {
Window window;
public:
Game();
};
Then you can call the constructor of the window class from the game constructor like so:
Game::Game() : window(DEFAULT_HEIGHT, DEFAULT_WIDTH) {}
In case you were talking about globals: If you really need a global object (although you probably don't want that) you can (and should!) declare the object with external linkage in the header (which will only make the name available, but not call any constructors) and do the definition in the implementation:
Declaration:
extern Window window;
Implementation:
Window window(DEFAULT_WIDTH, DEFAULT_HEIGHT);
Upvotes: 1
Reputation: 179907
You're apparently misunderstanding C++. You'd never have Window window;
just like that in a header. That defines a Window object, every time the header is included !
You may have class GameEngine { Window window; .... }
but that doesn't actually crate a window at all. Each GameEngine constructor has an initializer list, and there you do initialize window
. Makes sense: the game engine creates the window it needs.
Upvotes: 2