When to use pointers or objects in 'Composition'?

Started by
6 comments, last by plainoldcj 9 years, 2 months ago

Before I start please know I am a beginner so I may be asking a silly question or getting terminology wrong.

I have a simple program which initializes a win32 window, and then initializes directX 11 in that window.

The directX initialization is held in a class Graphics

The win32 window is held in a class MainWindow. My program flow goes as follows:


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    MSG msg;
    window = new MainWindow;
    window->initialize(hwnd, hInstance, nCmdShow);

the MainWindow::initialize function does all of that registering a win32 class and creating the window. At the bottom of this function is where I want to initialize directX within the window. This is where I get confused. As far as I am aware there seems to be three ways to do this.

METHOD 1. Use inheritance.


class MainWindow : public Graphics
{
private:
    HWND hwnd;
    HINSTANCE hInstance;
    HRESULT hr;
    int nCmdShow;

public:
    MainWindow();
    ~MainWindow();
    void initialize(HWND hwnd, HINSTANCE hInstance, int nCmdShow);
    static LRESULT CALLBACK WindowProc(HWND, UINT, WPARAM, LPARAM);
};

then after the ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); of my MainWindow::initialize() function, I can write.


    Graphics graphics;
    
    graphics.initialize(hwnd, 640, 480, false);
    graphics.showBackbuffer();

METHOD 2. Composition with an object.

Removing the inheritance line : public Graphics, means I need to put a Graphics object in my MainWindow member variable section e.g.


class MainWindow
{
private:
    HWND hwnd;
    HINSTANCE hInstance;
    Graphics graphics; <------------- ADDED THIS
    HRESULT hr;
    int nCmdShow;

I can now leave the same code as before, but remove the Graphics graphics; object declaration since it has already been done.

METHOD 3. Composition with a pointer to a graphics object.

The third method is to declare a pointer to a graphics object (which will be created later), in class MainWindow e.g.


class MainWindow
{
private:
    HWND hwnd;
    HINSTANCE hInstance;
    Graphics *graphics; <------------- ADDED THIS
    HRESULT hr;
    int nCmdShow;

This time, to initialize my directX window I write


    graphics = new Graphics();
    graphics->initialize(hwnd, 640, 480, false);
    graphics->showBackbuffer();

At this stage I am very confused. They all essentially get me where I want to be, but there would be a lot of behind the scenes stuff going on of which I have no idea.

So could someone clarify which method is best to use in this example?

Thanks

Advertisement
I like method two. The general rule of thumb is use pointers when you need to, and not when you don't.

Inheriting MainWindow from Graphics seems a bit wrong to me. MainWindow has a graphics interface, it isn't a graphics interface.

Method 3 requires you to delete the graphics, or store it in a smart pointer, for no actual benefit here that I can see. My own Application class contains a GraphicsDevice instance in the same way your Method 2 does. If your object constructors require arguments, you can just supply them in MainWindow initialiser list, so think carefully about the order you declare them in the class. They will be destroyed in reverse order they are declared so worth some thought there too.

E.g. if you have a TextureCache object that depends on the Graphics object, make sure it is declared after the Graphics object. Then you can safely pass the Graphics instance to the TextureCache constructor and be sure the TextureCache will be destroyed before the Graphics:
class MainWindow
{
    Graphics graphics;
    TextureCache texCache;
};

MainWindow::MainWindow() : texCache(graphics)
{
}
I am impressed that, as a beginner, you are designing in this way rather than falling back on global objects or their poor cousin, the Singleton. Good work! smile.png

E.g. if you have a TextureCache object that depends on the Graphics object, make sure it is declared after the Graphics object. Then you can safely pass the Graphics instance to the TextureCache constructor and be sure the TextureCache will be destroyed before the Graphics:

Most modern compilers will warn on this, thankfully.

In time the project grows, the ignorance of its devs it shows, with many a convoluted function, it plunges into deep compunction, the price of failure is high, Washu's mirth is nigh.

I would recommend separating responsabilities.

The graphics module doesn't need a reference to the window class, it only needs a window handle (on DirectX) or a handle to the device context (in OpenGL).

So, you pass a void* pointer to the graphics class, convert it to the desired handle, and initialize the graphics API.

Example:


bool Graphics::InitApi( void* _pvPtr ) {
           #ifdef DIRECTX
                   return DirectX::Init( reinterpret_cast<HWND>(_pvPtr) );   
           #elseif OPENGL
                   return OpenGl::Init( reinterpret_cast<HDC>(_pvPtr) );    
}

The advantage is that your graphics module it's abstracted and less dependent of your current platform. Just make sure to destroy the API right after the window is destroyed.

I would recommend separating responsabilities.

The graphics module doesn't need a reference to the window class, it only needs a window handle (on DirectX) or a handle to the device context (in OpenGL).

So, you pass a void* pointer to the graphics class, convert it to the desired handle, and initialize the graphics API.

Example:


bool Graphics::InitApi( void* _pvPtr ) {
           #ifdef DIRECTX
                   return DirectX::Init( reinterpret_cast<HWND>(_pvPtr) );   
           #elseif OPENGL
                   return OpenGl::Init( reinterpret_cast<HDC>(_pvPtr) );    
}

The advantage is that your graphics module it's abstracted and less dependent of your current platform. Just make sure to destroy the API right after the window is destroyed.

And the nicer way of achieving this is abstracting the HDC and DX device away in an interface that has platform specific implementations. This way you dont have to deal with the defines in systems that dont need to be aware of those defines.

Worked on titles: CMR:DiRT2, DiRT 3, DiRT: Showdown, GRID 2, theHunter, theHunter: Primal, Mad Max, Watch Dogs: Legion


And the nicer way of achieving this is abstracting the HDC and DX device away in an interface that has platform specific implementations. This way you dont have to deal with the defines in systems that dont need to be aware of those defines.

This is the responsability of the DirectX and OpenGl classes.

In all my stuff, my main Win32 window class and my main graphics object are actually completely unrelated. I just give the graphics constructor the HWND it needs.


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
    Window window(...);
    Graphics graphics(window.getHandle(), ...);
    while (window.processMessages())
    {
        ...
    }
}

I then have higher level logic that contains both these components to deal with both together (e.g. the game loop, managing window/fullscreen/borderless transitions, resizing, etc.).

I prefer method 2 for composition, although I sometimes use method 3 for the following reasons.

1. using pointers, you can forward declare class Graphics in the header to reduce dependencies, which in turn

reduces compile times when you modify class Graphics

2. using pointers, you can put all the initialize() work in the constructor and you don't have to worry about

your object being in invalid state. (in cases where you can't use initializer lists)

This topic is closed to new replies.

Advertisement