Using a class instead of array

Hi,

I am writting an utility library similar to D3DX for my new OpenGL engine.

I have noticed that a class can be given to OpenGL’s functions like glLightfv. The first member of the class will correspond to the elements of the array. It works, but is it a good idea ? Will it not be slower ?

Here is an example:

My class:

class GLGXVECTOR3{
public:
	union{
		float x;
		float u;
	};
	union{
		float y;
		float v;
	};
	union{
		float z;
		float r;
	};

	GLGXVECTOR3();
	GLGXVECTOR3( float x , float y , float z );
	~GLGXVECTOR3(){};

	GLGXVECTOR3 operator+ (GLGXVECTOR3 &B);
	GLGXVECTOR3 operator- (GLGXVECTOR3 &B);
	float	  operator* (GLGXVECTOR3 &B);
	GLGXVECTOR3 operator* (float factor);
	GLGXVECTOR3 operator/ (float factor);
};

The way i use it:

 
GLGXVECTOR3 Loc(0.0f,100.0f,0.0f);
glLightfv(GL_LIGHT0, GL_POSITION,(float*)&Loc);
 

Thanks.

It will not be slower, but it’s dangerous. You never know for sure how your compiler will pack class data into the class. This could go as far as breaking your code when you update to the next compiler version.

Better use something like that:

class GLGXVECTOR4 {
private:
    float vec[4];
public:
    ...
    float x() { return vec[0]; }
    ...
    operator float*() { return vec; }
    ...
}

Then you can use it:

GLGLXVECTOR4 Loc;
glLightfv(GL_LIGHT0, GL_POSITION, Loc);

This does not assume anything about the compiler dependant class layout, the speed is still the same, and as an additional bonus you don’t need an explicit typecast :wink:

The C++ ANSI/ISO specification does not guarantee that this will work, though it almost always will.

Unless you suddenly decide that GLGXVECTOR3 (horrible name, btw) needs to have virtual functions.

In general, you should not rely on this. Indeed, it’s better for you to have a specialized “glxLightPosition” function that takes this class natively. It could be inlined to the standard “glLightfv” class if you feel performance could be an issue.

Korval, i don’t want to write specialized “glxLightPosition” function because i want to be able to use my vector classes with any openGL function or non openGL function. (and it would be like rewritting a new 3D API, a thing i don’t want). And even with “inline”, i would have to do some conversion in the function which will not be better for performances.

i have used the overmind’s suggestion to write this:

class GLGXVECTOR4{
private:
	float vec[4];
public:
	union{
		float& x;
		float& u;
	};
	union{
		float& y;
		float& v;
	};
	union{
		float& z;
		float& r;
	};
	union{
		float& w;
		float& q;
	};

	GLGXVECTOR4():x(vec[0]),y(vec[1]),z(vec[2]),w(vec[3]),u(vec[0]),v(vec[1]),r(vec[2]),q(vec[3])
	{
		x=0.0f;y=0.0f;z=0.0f;w=0.0f;
	};

	GLGXVECTOR4( float x , float y , float z, float w ):x(vec[0]),y(vec[1]),z(vec[2]),w(vec[3]),u(vec[0]),v(vec[1]),r(vec[2]),q(vec[3])
	{ 
		this->x = x; this->y = y; this->z = z;this->w = w;
	}

	operator float*() 
	{ 
		return vec; 
	}
	
	GLGXVECTOR4 operator= (GLGXVECTOR4 &B)
	{
		if (this == &B) return B;
		this->x = B.x;
		this->y = B.y;
		this->z = B.z;
		this->w = B.w;
		return B;
	}
	~GLGXVECTOR4(){};

	GLGXVECTOR4 operator+ (GLGXVECTOR4 &B);
	GLGXVECTOR4 operator- (GLGXVECTOR4 &B);
	float	  operator* (GLGXVECTOR4 &B);
	GLGXVECTOR4 operator* (float factor);
	GLGXVECTOR4 operator/ (float factor);
};

It allow me to have direct access to the xyz variables ( Loc.x, Loc.y ,…), i can do sometings like v3 = v1 + v2 (where v1,v2,v3 are GLGXVECTOR4), and i can give the vector to the opengl function like glLightfv.

It seems to work.

Do you have other suggestions ? Is there something wrong in this class ?

There is a safe way to do what you originally wanted, and that’s to add a cast-to-float-pointer converter. Doing this always finds the right memory offset for your members and avoids the problems Korval mentioned.

Basically, just add to your class:

inline operator float*()             { return &x; }
inline operator const float*() const { return (const float*)&x; }

With that, you can call glVertex3fv(object) without even using the (float*) cast. Object is in this case not a pointer, but if it was, you would just dereference it.

Now you’re not dependent on where in your class the XYZ floats wind up, but you could potentially be burned if XYZ are not contiguous (which only really happens in unusual alignment schemes, say of multiple sub-structures). The safest way to prevent problems is to declare these members as a union of two or more structures, see below.

This also works for non-float types just as well. One thing to watch, though, is that you should make a habit of putting the ‘explicit’ keyword on constructors that might accidentally invoke this auto-conversion inappropriately.

I’ll give you my Vec3 template (minus the math functions) in case this helps:

template <class T> struct Vec3
{
	typedef T	ElemType;
	static uint ElemGLID;
	enum { ElemCount = 3 };

	Vec3<T>() { x = y = z = 0; }
        Vec3<T>(T x, T y, T z) : x(x), y(y), z(z) { }
	Vec3<T>(const Vec3<T>& u) : x(u.x), y(u.y), z(u.z) { }

	explicit Vec3<T>(const T  val) : x(val), y(val), z(val) { }
        explicit Vec3<T>(const T* xyz) : x(xyz[0]), y(xyz[1]), z(xyz[2]) { }
	explicit Vec3<T>(const Vec2<T>& u) : x(u.x), y(u.y), z((T)1) { }
	explicit Vec3<T>(const Vec4<T>&);

	inline void Set(T _x, T _y, T _z) { x = _x; y = _y; z = _z; }
	inline Vec3<T>& operator=(T val)  { Set(val,val,val); return *this; }
	inline Vec3<T>& operator=(const Vec3<T>& v) { Set(v.x,v.y,v.z); return *this; }
	inline Vec3<T>& operator=(const Vec4<T>& v);

    Bool operator==(const Vec3<T> & u) const
    {
        return (u.x == x && u.y == y && u.z == z) ? true : false;
    }

	typedef T* pointer;

	operator pointer()			   { return &x; }
	operator const pointer() const { return (const pointer)&x; }
	
    union {
        struct {
            T x,y,z;
        };
        struct {
            T s,t,u;
        };
        struct {
            T r,g,b;
        };
        T elem[3];     // array access
    };
};

Thanks. I will see what i can do with all that.

Basically, just add to your class: …
That’s equally dangerous. Ok, the offset to the first element is guaranteed to be correct, but the elements inside the struct are not guaranteed to be tightly packed. You’re still making assumptions that may be correct or not, depending on the compiler, architecture, operating system and so on…

I’ve just found an interesting article with this example:

struct A
{
  char c;
  int i;
}
struct B
{
  char c;
public:
  int i;
};

They say that: “a compiler could move B::i before B::c, but A::c must precede A::i.”

Full article here:
http://www.open-std.org/JTC1/sc22/wg21/docs/cwg_active.html

Another thing is that people rely on structure member order pretty much and I guess no one wants to make a compiler that will not be used by anyone. So this relation between member order in structure and real order in memory will be somewhat enforced by programmers upon compilers.

Just look at Windows API and DirectX - they rely on member order in structures. A great example would be vertex structure in Direct3D.

I have looked at the D3DX header, and they seems to use a basic x,y,z declaration in the structure, so they probably just return the adresse of x in “operator float*()”. They also use an union of “float _11,_12,…” and “float array[4][4]” for D3DXMATRIX. The memory is probably allocated in the same order.

But The DirectX SDK is written to be used on Windows and with Visual C++ only.

I want to create a library which could work with any operating system and compiler ( Windows, Haiku, Linux).

The use of references in initialization lists seems to be 100% safe but maybe not 100% optimized.

Ok, let’s make it official (I knew I read it somewhere :slight_smile: ):

Nonstatic data members of a (non-union) class declared without an intervening access-specifier are allocated so that later members have higher addresses within a class object.
This is from ISO/IEC 14882:1998.
Here is the link:
http://www.kuzbass.ru:8086/docs/isocpp/class.html

So if your compiler does not place structure/class member in proper order then report C++ standard violation to compiler developer :slight_smile:

I am not an expert in C++ and my english is not perfect. What does “non-union” mean" ?

Does your link mean that i can use something like that without any problem:

class GLGXVECTOR3{
public:
	union{
		struct {
			float x,y,z;
		}	
		struct {
			float u,v,r;
		}
	};

	GLGXVECTOR3();
	GLGXVECTOR3( float x , float y , float z );
	~GLGXVECTOR3(){};

	inline operator float*() { return &x; }
	inline operator const float*() const { return (const float*)&x; }

	GLGXVECTOR3 operator+ (GLGXVECTOR3 &B);
	GLGXVECTOR3 operator- (GLGXVECTOR3 &B);
	float	  operator* (GLGXVECTOR3 &B);
	GLGXVECTOR3 operator* (float factor);
	GLGXVECTOR3 operator/ (float factor);
};

Or, do i have to remove the union ?

And higher adresse is not a synonym of consecutive ?

Originally posted by k_szczech:
So if your compiler does not place structure/class member in proper order then report C++ standard violation to compiler developer :slight_smile: [/QB]
But the problem is alignment, not order. So three ubit16s might be padded to four byte boundaries i.e 2 bytes of padding.

You could use offsetof to check at compile time though.
–Stuart.

What does “non-union” mean" ?
From specs:

class-key:
class
struct
union

So “non-union class” is a class or structure.

Come to think of it… I’m still not sure about this whole member ofset thing :smiley: Look at this:

struct
{
 int x, y;
};

If structure is at address adr then if x is at address (adr + 100) and y is at address (adr + 1024) then it’s still compatible with what I quoted in my previous post.
Maybe there is some statement that members should be allocated at first possible location that is properly aligned? Need to read a bit further…

As for unions - same as above: we have to look for something in the specs about it. It may be that it’s required that members are as tightly packed as possible without breaking alignment rules. That’s what we want and that’s what we have to look for.
Ready… set… go!

Ok, more on unions.
Go to that link I posted and read Section 9.2 from -12- to -17-.
So that means first member is always at offset 0 and if you make union of two identical struct’s they will overlap properly.

We also know that members will be in the same order as dclared.

The only thing remaining is to find out is it allowed for compiler to make more gap between members than required to achieve proper alignment.

Searching…

Maybe there is some statement that members should be allocated at first possible location that is properly aligned? Need to read a bit further…
At least for non-uniforms, this is definitely not true, because there are some things that need to be stored at the beginning before other members (e.g. vtable or pointers to virtual base classes). This can be an arbitrary amount of data, especially with multiple inheritance.

The only thing remaining is to find out is it allowed for compiler to make more gap between members than required to achieve proper alignment.
I’m pretty sure it is allowed to do this, but I don’t know any reason why it should do it :wink:
Perhaps some debugging constructs, you never know with these “undefined” aspects of specifications…

I’m pretty sure it is allowed to do this, but I don’t know any reason why it should do it

Looks like. Although there is a statement that the only gap between members is the one necessarry to achieve required alignment.
So if the gap is not necessary then it’s not allowed?
So if I use byte-alignment I should have no gaps?
And if the gap is necessary then is it allowed for it to be bigger than necessary?

Looks like we almost have what we wanted but I found no place in specs that states that gaps are not allowed in other cases.
Well, but “almost have” means that we don’t have anything actually :smiley:

There is still the problem with padding structure to 16 bytes, but that can be solved with sizeof - you simply inform OpenGL that there is a gap between each vertex in the array you specified.

As for virtual functions - if you use proper type casting you’ll get pointer to first member and if you take sizeof into account then you’ll skip virtual function table of next object, too.
Anyway I think we can live without a virtual functions in a vec3, vec4 or mat4 classes, so let’s just pretend they don’t exist :slight_smile:

Ah, to hell with this. I’m gonna use structures and unions and don’t care. It’s gonna work anyway :slight_smile:
If the day comes when compilers will become “different” I will rewrite some fragments of my code. Since I tend to use layered design for my applications it shouldn’t be much work. It will probably come down to modifying wrapper for vertex array and loadin/saving data to file.

i don’t want to write specialized “glxLightPosition” function
You can always overload glLightfv and other functions.

A compiler can add padding more-or-less wherever and however it wants, for a number of reasons:

  1. Some processors (non-x86) can only address memory at certain multiples, so elements need to be padded

  2. some processors are more efficient at accessing memory at certain multiples, so elements are padded

  3. the compiler writers felt like it.

Most compilers have some way of overriding the default padding - for GCC, it would be:

class vec3
{
public:
  union
  {
    struct __attribute__ ((__packed__))
    {
      float x, y, z;
    }
    struct __attribute__ ((__packed__))
    {
      float r, g, b;
    }
    struct __attribute__ ((__packed__))
    {
      float s, t, p;
    }
  };
  ...
}

For MSVC, there’s a #pragma that you need to use. I don’t know about other compilers.

Well, #pragma is used to tell the compiler what is the required alignment. So If I choose alignment=2 then compiler can theoretically still align to 4 bytes, because if something is aligned to 4 bytes then it’s aligned to 2 bytes, too.

Perhaps attribute ((packed)) enforces no padding but I don’t know about any pragma that would do so.

I step away for a day and miss all the fun…

You guys are making this way too complicated. In the example I posted, there is a union of structs:

    union {
        struct {
            T x,y,z;
        };
        struct {
            T s,t,u;
        };
        struct {
            T r,g,b;
        };
        T elem[3];     // array access

If you’re concerned that x,y,z may not be tightly packed, just have the getter (the implicit type converter one I posted earlier) return the address of elem[0].

The array view of the union is guaranteed to be packed (the other elements are too in all normal cases) unless the following is true: you set your float alignment to 8 bytes. Floats will be correctly aligned and packed to 4-byte boundaries by default.

There are a million of lines of code out there that say “sizeof(array) = sizeof(elem) * length” or some variation. They wouldn’t work if arrays were allowed to vary in packing.