Sign in to follow this  
Nemesis2k2

Can your header file structure solve this cyclic dependency?

Recommended Posts

Nemesis2k2    1045
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
Miserable    606
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
Jingo    582
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
Miserable    606
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
3DNeophyte    122
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
Miserable    606
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
Miserable    606
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
Nemesis2k2    1045
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
dcosborn    674
Certainly C++ isn't the perfect language. But do you really want to work with some sort of convoluded include system just so you can save a few cycles with inlined functions? Its unfortunate that inline functions pollute interface-implementation separation, but there's really little you can do about that short of hacking your way around it which I guess is what you're trying to do. It just doesn't seem like you're going to find a clean solution that anybody would want to work with.

Share this post


Link to post
Share on other sites
Nemesis2k2    1045
Quote:
Certainly C++ isn't the perfect language. But do you really want to work with some sort of convoluded include system just so you can save a few cycles with inlined functions? Its unfortunate that inline functions pollute interface-implementation separation, but there's really little you can do about that short of hacking your way around it which I guess is what you're trying to do. It just doesn't seem like you're going to find a clean solution that anybody would want to work with.

The whole header system is a hack. I'm doing my best to minimize the amount of hacking I have to do on top of that. This is about structure. I'm designing an overall system which I feel comfortable working with, which lacks stupid problems like the one outlined here. I have a solution to this problem, and it's no more ugly than an include guard, which people seem to take for granted. It can't solve everything though. Namespaces are limited to a fraction of their potential power, as well as requiring some godawful hacks to implement in an at all abstract way. These problems cannot be solved with any header system. They are the result of a fundamental flaw with the header system itself.

Share this post


Link to post
Share on other sites
Extrarius    1412
You could always make the functions not inline, then use a compiler that supports 'link-stage inlining'(not sure of proper name) as I believe VS.Net 2002 and later do.

Share this post


Link to post
Share on other sites
Jingo    582
templates to the rescue..

I still think the problem is your choice of design. You have two closely related classes that are interconstructable, why not make them the same class?


template<std::size_t size>
struct Vector
{
float elements[size];
Vector(const Vector<size>&){...} // for vectors of the same size
template<std::size_t anothersize>
Vector(const Vector<anothersize>&){...} //for vectors of different sizes
};




Problem solved? No more cyclic dependancy in the headers, there is only one header, both functions are inline.

Share this post


Link to post
Share on other sites
dragongame    538
Hi,

this is my solution to your problem:

Vec3.h:

#ifndef _Vec3_h_
#define _Vec3_h_

class Vec4;

class Vec3
{
public:
inline void somefunc(Vec4& vec4);

float x,y,z;
};

#include "vec3.inl"




vec3.inl:

#include "Vec4.h"

void Vec3:somefunc(Vec4& vec4)
{

}



Vec4.h:

#ifndef _Vec4_h_
#define _Vec4_h_

class Vec3;

class Vec4
{
public:
inline void somefunc(Vec3& vec3);

float x,y,z,w;
};

#include "vec4.inl"




vec4.inl:

#include "Vec3.h"

void Vec4:somefunc(Vec3& vec3)
{

}



hope this helps.

Share this post


Link to post
Share on other sites
Nemesis2k2    1045
Quote:
I still think the problem is your choice of design. You have two closely related classes that are interconstructable, why not make them the same class?

No. Most definately no. First of all, I want to be able to use the .x, .y, .z, etc specifiers to access elements. Secondly, I don't want to have to use template specialization in order to implement all my functions.

Templates do solve this problem though. Remember that no code is created from a template until an object is created of that type. I can have this contradiction exist in two dependant templated classes, and it'll still work, because the template isn't parsed until I try and use it later on, by which time, both types have been fully defined. When I originally started thinking about this problem, I overlooked this, so I thought this would basically prevent two templated classes with this kind of dependency existing at all, because templated classes have to have all their functions defined in a header file. I was quite pleasently surprised to find this wasn't the case.

Quote:
Hi,

this is my solution to your problem:

That is an awesome idea, and the neat solution I was looking for. Rather than putting the #include inside the inline file though, I'd put it just above the include for the inline file itself. This keeps an inline file plain and simple, and means I can just make a small modification to the standard template I create a header from. Cheers dude, I can't believe I overlooked this simple solution.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this