Base and derived class field placement.

I am wondering if a variable (field) in a derived class always has the same offset as in the base class. For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class MyClassA
{
private:
	int64 Z;
	int64 A;
};

class MyClassB : MyClassA
{
protected:
	int64 B;
};

class MyClassC : MyClassB
{
private:
	int64 C;
};

class MyOtherClass 
{
private:
	int64 Y;
};

class MyClass D : MyClassA, MyOtherClass
{
	int64 D;
};


My question is would the A field always be located at the same offset from a class pointer to all of MyClassA, MyClassB, MyClassC, and MyClassD (and any other class that inherits A first)? If not, is there a way to force this to be so?

Reason is, I want A to be a union of the integer and a pointer so that I can implement a pooled stack of the objects when not in use, regardless of the size of the derived type.
Last edited on
I am wondering if a variable (field) in a derived class always has the same offset as in the base class. For example:

No. In general, (static_cast<derived*>(base_ptr) != base_ptr).

is there a way to force this to be so?

Not that I know of.
Last edited on
> I am wondering if a variable (field) in a derived class always has the same offset as in the base class.

If multiple inheritance and/or virtual inheritance is not involved, in practice, yes.
(The standard does not require it; but every implementation would opt for the most efficient object layout.)

A simple solution would be to provide (non-virtual) accessors in the base class. For example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct base
{
    // ...

    private:

        int& x() { return x_ ; }
        int x() const { return x_ ; }

        int& y() { return y_ ; }
        int y() const { return y_ ; }

        int x_ = 0 ;
        int y_ = 0 ;

    friend class pooled_stack_of_objects ;
};
@JLBorges
I believe I understand what you have done above. However, I am trying to think of how that would apply to a pointer. Can you have a pointer reference? Or should I just add a pointer to a pointer? Here is (some of) my code for the base object:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class IIMOECORE_EXPORT ManagedObject
{
private:
// save time (no virtual size function) but use more memory model:
#ifndef _LOWMEM_BUILD
	union
	{
		size_t _memSize;
		ManagedObject* _nextPoolObject;
	};
#endif

	friend class ObjectAllocator;

protected:
	union 
	{
		ID_STRUCT _id_properties;
		__uint64 _id;
// save memory but take more time model:
#ifdef _LOWMEM_BUILD
		ManagedObject* _nextPoolObject;
#endif
	}
};


I am not sure how I would designate a pointer reference (aren't & and * opposites?)
However, based on your suggestion would it work if I could add this to the above class and then dereference the pointer to a pointer so that I can assign to it in the pooled stack of objects class?

1
2
private:
	inline ManagedObject** nextPooledObject() { return &_nextPoolObject; }


OR would that bring us back to the same problem because we don't know which subclass of Managed Object I am using? If so then can I simply use what you wrote above (though using a 64 bit integer) and then explicitly cast the void pointer as a 64 bit integer when assigning?

The end objective is (hopefully) to group objects of the same size into the same memory resource pool, regardless of which implementation of ManagedObject is used. I can write an ObjectAllocator for each sub-class if necessary but given there will be hundreds of sub classes, that seems a bit extreme. It would be much simpler to manage pools by size if possible.
Last edited on
In case it helps... this is the allocator (new) and pooling (delete) code. This was based off the assumption that it always would be at the same offset. could. I am guessing this code will have to change... but it gives you the details on what I am trying to do. From my ObjectAllocator class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
inline void* createManagedRef()
{
	if (_pool == NULL)
	{
		++_referenceCount;
#ifdef _LOWMEM_BUILD
		return malloc(_size);
#else
		ManagedObject* p = static_cast<ManagedObject*>(malloc(_size));
		p->_memSize = _size;
		return p;
#endif
	}
	else
	{
		void* p = _pool;
		_pool = ((ManagedObject*)static_cast<ManagedObject*>
			(_pool))->_nextPoolObject;
		return p;
	}
}
		
inline void poolManagedRef(ManagedObject* p)
{
	p->_nextPoolObject = _pool;
	_pool = p;
}


All allocated space is returned using free when the application closes. I will add efficiency features as the project grows.
Last edited on
Something like this, perhaps:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
struct ManagedObject
{
    // ...

    private:

        enum type { SIZE, POINTER };

        type discriminant ;

        union
        {
            std::size_t sz ;
            ManagedObject* next ;
        };

        void set_size( std::size_t n ) { sz = n ; discriminant = SIZE ; }
        void set_pointer( ManagedObject* p ) { next = p ; discriminant = POINTER ; }

        std::size_t& get_size()
        {
            if( discriminant != SIZE ) sz = 0 ;
            discriminant = SIZE ;
            return sz ;
        }
        
        // return ManagedObject*& : reference to pointer
        ManagedObject*& get_next_pointer() 
        {
            if( discriminant != POINTER ) next = nullptr ;
            discriminant = POINTER ;
            return next ;
        }

    friend class ObjectAllocator ;
};


And then:
1
2
3
4
5
inline void poolManagedRef( ManagedObject* p )
{
	p->get_next_pointer() = _pool; // assign to lvalue (reference)
	_pool = p;
}
Can you have a pointer reference?

Of course you can. A pointer is just a type of variable. You can do the same things with it as you can with any other type. You can have a reference to it just like you can any other type of variable.

Or should I just add a pointer to a pointer?

What do you mean by that? What would it mean to add two pointers together?

Or are you asking if you can have a pointer to a pointer? If so, then, yes, of course you can. A pointer is just a type of variable. You can do the same things with it as you can with any other type. You can have a pointer to it just like you can any other type of variable.

aren't & and * opposites?

Huh? You seem very confused about what those symbols mean.

When using * as a unary operator, it dereferences a pointer operand. When using & as a unary operator, it gets the address of the operand. So, I suppose you could call the dereference operatore, and the address-of operator "opposites", in a sense.

But none of that has anything to do with using those symbols in a variable definition. When you use a * in a variable definition, you are not using the dereference operator. When you use a & in a variable definition, you are not using the address-of operator. So, in a variable definition, I can't imagine any sense in which you would call those symbols "opposites".
Last edited on
@MikeyBoy
I am familiar with their meaning and what they do. Opposites was the wrong word. It is just that in the past when they end up being used together (with a template for example), the compiler sometimes complains. I have never seen them actually used in combination correctly. I understand the concepts just fine... it is hard to explain but it takes me a bit to get used to using symbols in unusual combinations. (I am slow at symbolic translation... which makes me a slow reader).

@JLBprges
Thanks for the clear example!

EDIT: I assume it is ok to make these methods inline.
Last edited on
Topic archived. No new replies allowed.