🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

Is it ok to do this in C ++? Use of "class".

Started by
12 comments, last by Dawoodoz 4 years, 6 months ago

I am reading the book "Game Programming in C++" by Sanjay Madhav, and it is very frequent, almost constant, to see how instead of using includes in the interfaces of the classes, he uses the keyword "class" before the type that he intends to use so that the compiler accept it. I haven't seen it done anywhere else, and I don't know if it's okay. It works.

Does this technique have a name? Is it like forward declaration?

Example:


#pragma once
#include <vector>
#include "Math.h"
#include <cstdint>

class Actor
{
public:
	enum State
	{
		EActive,
		EPaused,
		EDead
	};

	Actor(class Game* game);
	virtual ~Actor();

	// Update function called from Game (not overridable)
	void Update(float deltaTime);
	// Updates all the components attached to the actor (not overridable)
	void UpdateComponents(float deltaTime);
	// Any actor-specific update code (overridable)
	virtual void UpdateActor(float deltaTime);

	// ProcessInput function called from Game (not overridable)
	void ProcessInput(const uint8_t* keyState);
	// Any actor-specific input code (overridable)
	virtual void ActorInput(const uint8_t* keyState);

	// Getters/setters
	const Vector3& GetPosition() const { return mPosition; }
	void SetPosition(const Vector3& pos) { mPosition = pos; mRecomputeWorldTransform = true; }
	float GetScale() const { return mScale; }
	void SetScale(float scale) { mScale = scale;  mRecomputeWorldTransform = true; }
	const Quaternion& GetRotation() const { return mRotation; }
	void SetRotation(const Quaternion& rotation) { mRotation = rotation;  mRecomputeWorldTransform = true; }
	
	void ComputeWorldTransform();
	const Matrix4& GetWorldTransform() const { return mWorldTransform; }

	Vector3 GetForward() const { return Vector3::Transform(Vector3::UnitX, mRotation); }

	State GetState() const { return mState; }
	void SetState(State state) { mState = state; }

	class Game* GetGame() { return mGame; }


	// Add/remove components
	void AddComponent(class Component* component);
	void RemoveComponent(class Component* component);
private:
	// Actor's state
	State mState;

	// Transform
	Matrix4 mWorldTransform;
	Vector3 mPosition;
	Quaternion mRotation;
	float mScale;
	bool mRecomputeWorldTransform;

	std::vector<class Component*> mComponents;
	class Game* mGame;
};

 

Advertisement

I have not seen this kind of forward declaration before, but if you look here it seems like it is possible.

From the link:


class U;
namespace ns{
    class Y f(class T p); // declares function ns::f and declares ns::T and ns::Y
    class U f(); // U refers to ::U
    Y* p; T* q; // can use pointers and references to T and Y
}

However, the only reason I can see why you should use such a "local" forward declaration is to avoid polluting the global namespace with forward declarations.

Greetings

Yes, it works, but in my opinion it is extremely bad style. If that code must ever be maintained or refactored, it is better to have the forward declarations where they belong to. In a complex system there'd be different namespaces and then it would look even uglier.

Many examples in C++ are written in a bad style ... that sometimes stands in the way of understanding what's going on, stuffing too much functionality in a single class, using uninitialized pointers, and all that spectrum. Examples i have: "Learning Vulkan" from packtpub, or the reference implementation of CDLOD.

Am i alone with that ?

To be honest, with my moderate dislike for C++ I think this is rather elegant. Forward declarations have always bothered me as ugly and meaningless statements. They're basically just red tape.

But I too may be completely alone with that vision.

I agree with Green_Baron. Yes it works, yes it's technically valid C++ and yes there are plenty of examples of it being used (I'd add Unreal Engine to the list there). However I think it's poor style, I don't use the practice on my projects and generally discouraged it's use in our coding style for the project I'm on at work.

Firstly I believe it's very helpful to have all the dependencies basically listed at the top of the tile (either as an include or forward declaration).  Secondly I don't see the need to repeat oneself when the type is used multiple times within the same header. Thirdly it's incompatible with types in namespaces. At the very least an inline forward declaration of a namespaced class would look ridiculous, but there may not be a syntax that actually compiles.  Lastly it reduces the changes required for refactors as you only have to change the one declaration instead of every instance in the file.

As for it polluting the global namespace, I don't see a forward declaration as doing that. If the actual declaration is in the global namespace you've already "polluted" it. Adding the type to the space again doesn't pollute it more. If you're running into a typename collision because of forward declarations, you're going to eventually run into a typename collision because of the actual declarations.

I am generally willing to accept it in the Unreal code I work with either because a) it's not worth screwing up integration merges from Epic or b) it's auto=generated code so the auto-generation code would replace them on a refactor and it arguably makes it easier to write the generation if you're not having to do multiple passes to figure out dependencies just to generate forward declarations.

--Russell Aasland
--Lead Gameplay Engineer
--Firaxis Games

5 hours ago, MagForceSeven said:

As for it polluting the global namespace, I don't see a forward declaration as doing that.

I generally agree with your argumentation and won't use that kind of forward declaration either for the reasons you mentioned, but I can see some rare cases where it might make sense. Under the assumption, that this kind of forward declaration is only defined in the scope of the class and does not spill over to the global namespace (have not tested it, so everything that follows is only valid if the assumption holds) it might prevent some hard to track bugs.

For example, if you have a forward declaration in one of your headers and rename the corresponding class (replace a lower case letter with an upper case letter), but forget to update the forward declaration and some other locations, you might get some undefined reference errors that are hard to track. You just see that there is an Include of the corresponding class in your file and 2 lines below you want to use it but you get an error and don't know why. Reason: Another included header provides the wrong forward declaration and you don't see that you used the old classes name which is nearly identical. If the forward declaration is in place, you might see it much easier. However, this is more a try to find a reason why it is even possible instead of a real argument.

Greetings

I see several uses.

Most people are used to class declarations, like this one at the top of your posting:


class Actor
{ ... }

This use declares that the type exists, and either has a body like the one above, or it can be a forward declaration, like this:


class Actor;

These are the most common uses.  Hopefully you know enough c++ to know how both of these work.

 

Next, I see an elaborated type specifier. These are somewhat less common in human-generated code, but very common in computer-generated code:


Actor(class Game* game) ;

Sometimes a name gets reused in a way that lets the compiler get confused.  As an example, it is possible to use a name for a class type name, and to use it for the name of an object such as a parameter, and to use it as a name for a function, to name a few.

For example:


class foo {};  // declare a class named foo
void foo(class foo) {}  // Declare a function named foo that accepts an unnamed parameter of type foo
int main() {
   class foo f;  // Elaborated type tells the compiler that "foo" refers to the class
   foo(f);  // call the foo function with a foo object named f as a foo parameter. 
}

Elaborated type names like that can help avoid problems with commonly used names, and in your code snippet "Game" is likely heavily used.  It might not be necessary, or in their code base it might have been used to name something that caused confusion in the compiler. 

It doesn't hurt anything to leave in an elaborated type specifier, so they're especially common in generated code since it is easy to add while reducing the risk of unexpected name conflicts.

 

Finally, the code uses it as a template's type parameter:


std::vector<class Component*> mComponents;

In this situation the language allows the word class or typename interchangeably, to specify that the template parameter refers to a type instead of any of the other template parameter possibilities.

 

 

Hi. Not sure what your question is. If you ask me the human generated examples are much better readable, then the option that's generated automatically (but this is just my opinion :) )

Crealysm game & engine development: http://www.crealysm.com

Looking for a passionate, disciplined and structured producer? PM me

cozzie said:

Hi. Not sure what your question is. If you ask me the human generated examples are much better readable, then the option that's generated automatically (but this is just my opinion :) )

The question was specifically about the use of class in function prototypes etc.

I think the only reason that auto-generated code was mentioned is because it's easier for generated code to just spit out the extra disambiguating syntax like this than to try and do it any other way.

Goyira said:
to see how instead of using includes in the interfaces of the classes


Prototype said:
Forward declarations have always bothered me as ugly and meaningless statements. They're basically just red tape.


Usually the reason to use forward declarations is avoiding unnecessary includes to reduce compile time to its minimum.

Having forward declaration in header files, and including only in cpp means you can include the header file elsewhere without any unrelated dependencies and bloat.

(The same advantage then applies in making the code more readable - forward means you do not have to worry about details yet.)

This topic is closed to new replies.

Advertisement