When/How often should I be using forward declaration?

Started by
7 comments, last by cmac 6 years, 5 months ago

So I recently ran into a problem where my Game class needed to know about my GraphicDevice class (Its being used as member variable) and my GraphicsDevice class needed to know about my Game class (Its being used as a method param)


//In my Game.h file
#include <Windows.h>
#include "GraphicsDevice.h"
class Game
{
public:
	/*Other methods and things*/
	GraphicsDevice graphicsDevice;
};

//In my GraphicsDevice.h file
#include <d3d11.h>
#include "Game.h"
class GraphicsDevice
{
public:
  	/*Other stuff*/
	void init(Game* game);
};

Now I know this wont compile because it causes a circular dependency issue and that it can be easily be solved with forward declaration

But it started to make me wonder, should I be forward declaring everything? Not only classes but structs too? EG:


//In GraphicsDevice.h
//Structs from d3d11.h (directx 11 header)
//Not directly used in the header file, but needed to be known for the member variables
struct IDXGISwapChain;
struct ID3D11Device;
struct ID3D11DeviceContext;
struct ID3D11RenderTargetView;
class GraphicsDevice
{

public:
	/* Other methods and stuff */
  
	IDXGISwapChain* swapchain;
	ID3D11Device* device;
	ID3D11DeviceContext* deviceContext;
	ID3D11RenderTargetView* backBuffer;
};

 

Normally I just include the header file, but I'm not sure if this is correct. Should I be making DTO like class instead and the use that for the properties that I need to pass from Game to GraphicsDevice?

Advertisement

My personal preference is to forward declare only what I need to use, and then promote to a full dependency as infrequently as possible.

So if you need to use another type, forward declare when you can, but don't just throw around declarations for the fun of it.

Wielder of the Sacred Wands
[Work - ArenaNet] [Epoch Language] [Scribblings]

Why does GraphicsDevice need to know anything about Game? The game should be responsible for configuring the device. Any parameters, etc needed by the device should be passed into the device (e.g. pass GraphicsDeviceInfo* instead of Game* to init()). Try to think of your system design as a hierarchy/tree of complexity, with low level modules at the leaves and high level modules (e.g. Game) at the top. Each level in the tree should only know about its direct dependencies (its children).

As for forward declarations, I use them as needed whenever you have circular dependencies, or when you want to hide the implementation (PIMPL idiom). Keeping includes in source files and forward declaring in headers can also reduce build times and public dependencies (e.g. I keep all <windows.h> includes in the source files to avoid polluting the global namespace).

31 minutes ago, Aressera said:

As for forward declarations, I use them as needed whenever you have circular dependencies, or when you want to hide the implementation (PIMPL idiom).

Yep. In the OPs GraphicsDevice class, those DX structures are irrelevant to the user of the class. PIMPL will keep them out of the header that the user will consume (see also using an interface instead of a concrete class).

The one issue with forward declarations is they violate DRY, especially in the case of templated classes. In that case, it's often useful to have a separate Class_decl.h that just includes the declaration.

if you think programming is like sex, you probably haven't done much of either.-------------- - capn_midnight

 

As a short-term fix for the problem which's long-term fix is to design better. But long-term solutions are for refactoring or re-implementing, not for prototyping, which is why 3-tier solutions like the above are useful.

RIP GameDev.net: launched 2 unusably-broken forum engines in as many years, and now has ceased operating as a forum at all, happy to remain naught but an advertising platform with an attached social media presense, headed by a staff who by their own admission have no idea what their userbase wants or expects.Here's to the good times; shame they exist in the past.

I personally use include statetemnts in header files as least as possible and move anything that is not needed to be outer visible to the implementation file as already mentioned. This includes also platform dependant headers like the windows.h file but also types/utilities that are used in functions internally only.

But I have had also implementation specific circular dependencies to solve that were caused by object/manager relations where I want the manager class to have friend access to single properties of the object for setting a class private API handle with external read-only access. Those were a few simple ones like GraphicsContext <> Graphics and InputDevice <> Input relations (as already written, to set a class private handle used by the API).

Otherwise except those rare situations like making some functions friend access to a class, I would always prevent circular dependencies. Another exception to use forward declaration over header includes is for template heavy classes like for example delegates. Declaring a delegate class


delegate<returnType (functionArgs)> myDelegate;

as forward declaration only and later specialize it so that the above signature works


template<typename signature> struct delegate;

//in a loop for any number of arguments
template<typename returnType, typename Argument> struct delegate<returnType (Argument)>
{
  ...
};

 

On 9/11/2017 at 6:28 AM, Wyrframe said:

 

As a short-term fix for the problem which's long-term fix is to design better. But long-term solutions are for refactoring or re-implementing, not for prototyping, which is why 3-tier solutions like the above are useful.

I didn't know obout <iosfwd>. Good talk!

Not a C++ expert (yet), but this is what I've gathered from college + research:

Include headers in headers as little as possible in general. At best compile times are slowed, at worst you end up with obscure circular includes and namespace pollution.

Forward declare in headers when you need to declare a pointer to a type, but do not need access to any of its information within the header file. Include the forward declared class in the .cpp file so you can access its data and operate on it. This is the easiest way to deal with classes including each other, in my experience at least. In your Game/GraphicsDevice example, Game.h will need to include GraphicsDevice because it is declared as a value type, but since GraphicsDevice only declares a pointer to Game, it can be forward declared and then included in the cpp file.

I've personally found these two rules really easy to follow and handles the majority of all cases, though cases like template classes can get tricky if their implementations are inline.

This topic is closed to new replies.

Advertisement