Jump to content
  • Advertisement
Sign in to follow this  
noodleBowl

C++ When/How often should I be using forward declaration?

This topic is 372 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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?

Edited by noodleBowl

Share this post


Link to post
Share on other sites
Advertisement

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).

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites

 

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.

Edited by Wyrframe

Share this post


Link to post
Share on other sites

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)>
{
  ...
};

 

Edited by Shaarigan

Share this post


Link to post
Share on other sites
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!

Share this post


Link to post
Share on other sites

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.

Edited by cmac

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!