Jump to content
  • Advertisement
Sign in to follow this  
Nemesis2k2

Can your header file structure solve this cyclic dependency?

This topic is 4871 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

In light of a few limitations I've noticed in the way I used to structure my header files, I've been designing a new system for organising my code. I came up with an acid test scenario which plagued hell with most of my designs, and I think it would be a really good one for other people to consider, to see how good their systems really are, so I thought I'd share it with everyone. The scenario is this. You have two non-templated container classes. Let's say a Vector3 and a Vector4. Both of them have functions which take an object of the other type as an argument, and access internal members of that object. These functions are marked as inline, so the full definitions for these functions are given in inline files. The traditional headers for these two classes are as follows: vector3.h
#ifndef __VECTOR3_H__
#define __VECTOR3_H__
#include "vector4.h"

class Vector3
{
public:
	inline void SomeFunc(Vector4& vec4);

	float x, y, z;
};

#include "vector3.inl"
#endif

vector3.inl
void Vector3::SomeFunc(Vector4& vec4)
{
	vec4.x;
	vec4.y;
	vec4.z;
	vec4.w;
}

vector4.h
#ifndef __VECTOR4_H__
#define __VECTOR4_H__
#include "vector3.h"

class Vector4
{
public:
	inline void SomeFunc(Vector3& vec4);

	float x, y, z, w;
};

#include "vector4.inl"
#endif

vector4.inl
void Vector4::SomeFunc(Vector3& vec3)
{
	vec3.x;
	vec3.y;
	vec3.z;
}

This will not work of course. The SomeFunc() prototype tries to use the name of the other class, which has not been declared yet. This is easy enough to solve. Prototypes simply need to be given of both classes, before the other class it is dependent on gets included. That makes vector3.h and vector4.h now look something like this: vector3.h
#ifndef __VECTOR3_H__
#define __VECTOR3_H__
class Vector3;
#include "vector4.h"

class Vector3
{
public:
	inline void SomeFunc(Vector4& vec4);

	float x, y, z;
};

#include "vector3.inl"
#endif

vector4.h
#ifndef __VECTOR4_H__
#define __VECTOR4_H__
class Vector4;
#include "vector3.h"

class Vector4
{
public:
	inline void SomeFunc(Vector3& vec4);

	float x, y, z, w;
};

#include "vector4.inl"
#endif

This still won't work however. The problem in this case is the inlined function. Let's say that the vector4.h file is being included in a project. The prototype for the Vector4 class is given, then the vector3.h file is included. The SomeFunc() function in Vector3 accesses members of the object of type Vector4 that gets supplied to it. In order to do that, it needs the complete definition of Vector4 to have already been given. While the prototype of the function given within the body of the Vector3 class can make do with the class prototype of Vector4 that has been supplied, the actual body of the function, defined within the vector3.inl file, needs the full body of the Vector4 class to have been defined, so that it knows what the members of the class are. This means that neither inline file can be included, until the full definitions for both the Vector3 and Vector4 classes are given. This is the code the compiler needs to see:
class Vector3;
class Vector4;


class Vector3
{
public:
	inline void SomeFunc(Vector4& vec4);

	float x, y, z;
};
class Vector4
{
public:
	inline void SomeFunc(Vector3& vec4);

	float x, y, z, w;
};


void Vector3::SomeFunc(Vector4& vec4)
{
	vec4.x;
	vec4.y;
	vec4.z;
	vec4.w;
}
void Vector4::SomeFunc(Vector3& vec3)
{
	vec3.x;
	vec3.y;
	vec3.z;
}

The header files basically need to be interleaved together. When you have a single "include everything for this class" file though, you can't really do that. It gets even more annoying if I was to throw in a Vector2 class as well. This is a really nasty cycle, and while I have a solution, it's complicated. It's neat, and it's powerful, but it's complicated. I just thought I'd post this little dilemma here for the benefit of others, and see if anyone else has a cool solution to this problem that I might have overlooked. And on a related note, I can't help but wonder why we're all still using header files for C++. A proper "module" of sorts would make life infinitely easier.

Share this post


Link to post
Share on other sites
Advertisement
This is one reason not to explicitly inline all your functions unless your profiler tells you that you really need to.

Of course, proper modules would be nice. C++ suffers greatly from its antiquated compilation model.

Share this post


Link to post
Share on other sites
I think the problem lies in your poor interface of each class. Why does Somefunc need to be a member function of either class? Surely there is no reason for a vector3 to be related to a vector4 in its class interface. Any function i can think of can be implemented as an inline non-member-function. Doing this would remove your cycle.

Share this post


Link to post
Share on other sites
You may have a point, Jingo, but his vectors were really just an example of a potential dependency problem. Consider any two classes Foo and Bar that operate upon one another; I'm sure there we could think of some examples where it is motivated.

Share this post


Link to post
Share on other sites
Feel free to give another example then, and im sure we can think of a better solution to the problem. =P

Share this post


Link to post
Share on other sites
vector3.h should forward declare the vector4 class, not the vector3 class. Same goes for vector4.h. I just coded this example, and it worked fine.

EDIT:
for example.....

#ifndef __VECTOR4_H__
#define __VECTOR4_H__

#include "vector3.h"

class Vector3;

class Vector4
{
public:
inline void SomeFunc(Vector3& vec4);

float x, y, z, w;
};

....
/EDIT


p.s. I didn't use that #include "soandso.inl" stuff - I just used .cpp files for the class defs.

Share this post


Link to post
Share on other sites
Quote:
Original post by 3DNeophyte
p.s. I didn't use that #include "soandso.inl" stuff - I just used .cpp files for the class defs.

And that's why it worked, but then your functions cannot be explicitly inlined—such functions must be implemented in headers. (And this is why I said that we shouldn't inline everything. When all functions are implemented in source files rather than headers, the problem goes away.)

Like the OP said, forward declarations are insufficient since the functions manipulate elements of the respective classes and must therefore have knowledge of their internals.

Share this post


Link to post
Share on other sites
Defining it in the same header will lead to the exact same problems as defining it in multiple headers or a header and an inlined file, the only difference being that you can't create messy reordering hacks to get around them.

Share this post


Link to post
Share on other sites
Quote:
Original post by Jingo
I think the problem lies in your poor interface of each class. Why does Somefunc need to be a member function of either class? Surely there is no reason for a vector3 to be related to a vector4 in its class interface. Any function i can think of can be implemented as an inline non-member-function. Doing this would remove your cycle.

It was an example, not a real system. If you want a real example as to why they might want to be related though, how about a constructor. Vector3 has a constructor that can take a Vector4 as an argument, and fills the Vector3 with the corresponding components from the Vector4, minus the w component. Vector4 has a constructor that can take a Vector3 as an argument, as well as a single extra parameter to specify the w coordinate.

And sure, removing the explicit inline directive would solve the problem. That's not the point though. I shouldn't have to take into account factors which are entirely internal to a class, in order to make sure I can use it in conjunction with others. The fact that I have to, shows there's a problem. Why should the ability of Vector3 and Vector4 to interface be impaired, simply because Vector3 decides to inline a function?

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.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!