Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Multiple inheritance and pointer equivalence

1 view
Skip to first unread message

Danny Woods

unread,
Nov 18, 2009, 6:20:06 AM11/18/09
to

Hi all,

Given this simple program:

----

#include <cstdio>

class A { public: virtual ~A() {} };
class B { public: virtual ~B() {} };
class C : public A, public B { public: virtual ~C() {} };

int main(void)
{
C *c = new C();
A *a = c;
B *b = c;

printf("c: %p; a: %p; b: %p\n", c, a, b);

delete c;
return 0;
}

----

Is it to be expected that the addresses stored in a and b are different?
I've tried this with Visual C++ and Cygwin g++, with identical results.

The problem I have is that there are other subclasses of A and B that
are distinct, but that there's a special case where the combined
subclass, C, is required to fill both roles. When the code that cleans
up a and b runs later, I'll end up with double deletion unless I can
reliably tell that a and b point to the same object, but the simple
'a == b' doesn't work here.

Cheers,
Danny.

Alf P. Steinbach

unread,
Nov 18, 2009, 7:01:34 AM11/18/09
to
* Danny Woods:

> Hi all,
>
> Given this simple program:
>
> ----
>
> #include <cstdio>
>
> class A { public: virtual ~A() {} };
> class B { public: virtual ~B() {} };
> class C : public A, public B { public: virtual ~C() {} };
>
> int main(void)
> {
> C *c = new C();
> A *a = c;
> B *b = c;
>
> printf("c: %p; a: %p; b: %p\n", c, a, b);
>
> delete c;
> return 0;
> }
>
> ----
>
> Is it to be expected that the addresses stored in a and b are different?

Of course.

In general they must be, since those are pointers directly to A and B objects.


> I've tried this with Visual C++ and Cygwin g++, with identical results.
>
> The problem I have is that there are other subclasses of A and B that
> are distinct, but that there's a special case where the combined
> subclass, C, is required to fill both roles. When the code that cleans
> up a and b runs later, I'll end up with double deletion

Try C++ destructors, they're good at cleanup.


> unless I can
> reliably tell that a and b point to the same object, but the simple
> 'a == b' doesn't work here.

Don't do that.


Cheers & hth.,

- Alf

Danny Woods

unread,
Nov 18, 2009, 7:52:28 AM11/18/09
to
"Alf P. Steinbach" <al...@start.no> writes:

> * Danny Woods:


> Is it to be expected that the addresses stored in a and b are
> different?
>
> Of course.
>
> In general they must be, since those are pointers directly to A and B
> objects.

Even though they're pointing to the same underlying instance of a C?
From an object oriented perspective, I wouldn't have expected that.
Something learned!

> Try C++ destructors, they're good at cleanup.

The destructor is the issue. Let me elaborate. This is in the context
of an embedded audio/video decompression/rendering system. Almost all
object allocation is done up-front in order to minimise
allocation induced fragmentation (the system has no MMU). One component
decides which audio and video renderers to use, based upon configuration
information. It then hands these instances down to an AVSync object,
after which it has nothing more to do with them. So the AVSync object
has two pointer members: the video renderer and the audio renderer. In
the special case I describe, a *single* instance is both the audio and
video renderer, implementing both interfaces.

Occasionally, it is necessary to destroy the AVSync object and create a
new one (expensive, but unavoidable). In the destructor for AVSync, I
can't simply delete the audio and video renderers if they're both
pointing to the same underlying object.

>
>
>> unless I can
>> reliably tell that a and b point to the same object, but the simple
>> 'a == b' doesn't work here.
>
> Don't do that.

Alternatives?

Cheers,
Danny.

Alf P. Steinbach

unread,
Nov 18, 2009, 8:15:57 AM11/18/09
to
* Danny Woods:

Aw shucks, hell[1], I'd describe how to do separation of concerns, and a simple
one would be to bundle those two pointers in an object that knows how to destroy
them (individually or as one). But I don't have time and not sure you'd see the
wisdom of that from just a Usenet posting. So, since they have virtual members
you can just do dynamic_cast<void*>(a) == dynamic_cast<void*>(b). :-)

Cheers & hth.,

- Alf


Notes:
[1] Trying out the ever more popular "mixing of curse levels".

Stuart Golodetz

unread,
Nov 18, 2009, 10:54:33 AM11/18/09
to
Danny Woods wrote:
> "Alf P. Steinbach" <al...@start.no> writes:
>
>> * Danny Woods:
>> Is it to be expected that the addresses stored in a and b are
>> different?
>>
>> Of course.
>>
>> In general they must be, since those are pointers directly to A and B
>> objects.
>
> Even though they're pointing to the same underlying instance of a C?
> From an object oriented perspective, I wouldn't have expected that.
> Something learned!

They're pointing to the A and B subobjects, which won't be at the same
address. There's a distinction between where they're logically pointing
(at the C object) and where they're physically pointing (wherever's
necessary within that C object to make everything work).

>> Try C++ destructors, they're good at cleanup.
>
> The destructor is the issue. Let me elaborate. This is in the context
> of an embedded audio/video decompression/rendering system. Almost all
> object allocation is done up-front in order to minimise
> allocation induced fragmentation (the system has no MMU). One component
> decides which audio and video renderers to use, based upon configuration
> information. It then hands these instances down to an AVSync object,
> after which it has nothing more to do with them. So the AVSync object
> has two pointer members: the video renderer and the audio renderer. In
> the special case I describe, a *single* instance is both the audio and
> video renderer, implementing both interfaces.
>
> Occasionally, it is necessary to destroy the AVSync object and create a
> new one (expensive, but unavoidable). In the destructor for AVSync, I
> can't simply delete the audio and video renderers if they're both
> pointing to the same underlying object.
>
>>
>>> unless I can
>>> reliably tell that a and b point to the same object, but the simple
>>> 'a == b' doesn't work here.
>> Don't do that.
>
> Alternatives?
>
> Cheers,
> Danny.

Hint: If you can use boost::shared_ptr in your project, that would make
it a lot easier.

Here's an example:

#include <iostream>

#include <boost/shared_ptr.hpp>
using boost::shared_ptr;

struct AudioRenderer
{
virtual ~AudioRenderer() { std::cout << "~AudioRenderer()\n"; }
};

struct VideoRenderer
{
virtual ~VideoRenderer() { std::cout << "~VideoRenderer()\n"; }
};

struct AVRenderer : AudioRenderer, VideoRenderer
{
~AVRenderer() { std::cout << "~AVRenderer()\n"; }
};

struct AVSync
{
shared_ptr<AudioRenderer> m_audioRenderer;
shared_ptr<VideoRenderer> m_videoRenderer;

AVSync( const shared_ptr<AudioRenderer>& audioRenderer,
const shared_ptr<VideoRenderer>& videoRenderer)
: m_audioRenderer(audioRenderer),
m_videoRenderer(videoRenderer)
{}
};

int main()
{
shared_ptr<AVSync> avSync;

// Introduce a local scope so that main isn't holding a handle
// to the renderers still.
{
shared_ptr<AVRenderer> avRenderer(new AVRenderer);
avSync.reset(new AVSync(avRenderer, avRenderer));
}

// Similarly.
{
shared_ptr<AudioRenderer> aRenderer(new AudioRenderer);
shared_ptr<VideoRenderer> vRenderer(new VideoRenderer);
avSync.reset(new AVSync(aRenderer, vRenderer));
}

return 0;
}

Regards,
Stu

Danny Woods

unread,
Nov 18, 2009, 11:33:53 AM11/18/09
to
Stuart Golodetz <sgol...@NdOiSaPlA.pMiPpLeExA.ScEom> writes:

> Danny Woods wrote:
>>
>> Even though they're pointing to the same underlying instance of a C?
>> From an object oriented perspective, I wouldn't have expected that.
>> Something learned!
>
> They're pointing to the A and B subobjects, which won't be at the same
> address. There's a distinction between where they're logically
> pointing (at the C object) and where they're physically pointing
> (wherever's necessary within that C object to make everything work).

Hi Stu,

That difference is what was tripping me up, since it kinda runs contrary
to normal object oriented idioms. Conceptually, the subclass pointers
point to the same instance that was created from the superclass, and
having them offset from each other because of implementation/layout
concerns--and having that visible from outside the instance--breaks that
model.

> Hint: If you can use boost::shared_ptr in your project, that would
> make it a lot easier.

Thanks for taking the time to write your sample code; I'll certainly see
what I can borrow from it. I've yet to try compiling boost on my target
platform (single-core Blackfin 537 running uClibc based, MMU-less
Linux). Maybe I'll get a chance to use it if I can squeeze it on. :-)

Cheers,
Danny.

Vladimir Jovic

unread,
Nov 18, 2009, 11:54:05 AM11/18/09
to
Alf P. Steinbach wrote:
>
> Aw shucks, hell[1], I'd describe how to do separation of concerns, and a
> simple one would be to bundle those two pointers in an object that knows
> how to destroy them (individually or as one). But I don't have time and
> not sure you'd see the wisdom of that from just a Usenet posting. So,
> since they have virtual members you can just do dynamic_cast<void*>(a)
> == dynamic_cast<void*>(b). :-)

o_O
Why???
How is that working???


--
Bolje je ziveti sto godina kao bogatun, nego jedan dan kao siromah!

Stuart Golodetz

unread,
Nov 18, 2009, 11:55:25 AM11/18/09
to

For what it's worth, you don't need to compile Boost to use things like
shared_ptr: if all else fails, you can just copy the relevant headers
into a suitable directory.

Stu

David Harmon

unread,
Nov 19, 2009, 11:05:14 AM11/19/09
to
On Wed, 18 Nov 2009 11:20:06 +0000 in comp.lang.c++, Danny Woods
<danny...@yahoo.co.uk> wrote,

>subclass, C, is required to fill both roles. When the code that cleans
>up a and b runs later, I'll end up with double deletion unless I can
>reliably tell that a and b point to the same object, but the simple
>'a == b' doesn't work here.

You cannot delete a or b here. You can only pass to delete the value
returned by new, which is c. There is no reason to expect either a or
be to be the same value.

Thomas J. Gritzan

unread,
Nov 19, 2009, 12:33:22 PM11/19/09
to
David Harmon schrieb:

> On Wed, 18 Nov 2009 11:20:06 +0000 in comp.lang.c++, Danny Woods
> <danny...@yahoo.co.uk> wrote,
>> subclass, C, is required to fill both roles. When the code that cleans
>> up a and b runs later, I'll end up with double deletion unless I can
>> reliably tell that a and b point to the same object, but the simple
>> 'a == b' doesn't work here.
>
> You cannot delete a or b here. You can only pass to delete the value
> returned by new, which is c.

Not true. The destructors of A and B are virtual, so delete a; or delete
b; would be fine.

--
Thomas

Juha Nieminen

unread,
Nov 19, 2009, 4:52:29 PM11/19/09
to
Danny Woods wrote:
> The problem I have is that there are other subclasses of A and B that
> are distinct, but that there's a special case where the combined
> subclass, C, is required to fill both roles. When the code that cleans
> up a and b runs later, I'll end up with double deletion unless I can
> reliably tell that a and b point to the same object, but the simple
> 'a == b' doesn't work here.

Having to do an explicit delete on such pointer, rather than it being
automatic, feels like a dubious design, but anyways you can do this:

if(a == dynamic_cast<A*>(b)) ...

If 'b' points to an object which has been multiple-inherited from both
A and B, then the dynamic_cast will succeed (else it will return 0),
after which you can compare it to 'a'. (Such a dynamic_cast requires for
at least one of the members of A and B to be virtual, but since your
destructors are, it's not a problem.)

Juha Nieminen

unread,
Nov 19, 2009, 4:53:51 PM11/19/09
to
Alf P. Steinbach wrote:
> So,
> since they have virtual members you can just do dynamic_cast<void*>(a)
> == dynamic_cast<void*>(b). :-)

Why would that work? How can you even dynamic-cast to void*? What
would that even *mean*?

Anyways, it's much easier to do it like:

Alf P. Steinbach

unread,
Nov 19, 2009, 5:15:48 PM11/19/09
to
* Juha Nieminen:

> Alf P. Steinbach wrote:
>> So,
>> since they have virtual members you can just do dynamic_cast<void*>(a)
>> == dynamic_cast<void*>(b). :-)
>
> Why would that work? How can you even dynamic-cast to void*? What
> would that even *mean*?

It gives you the address of the most derived object[1].

You can do it because the Holy Standard says so.

And that also formally answers why it would work, but the in-practice answer for
that also involves "because if you're using a compiler like MSVC then you have
provided the options that make it mostly standard-compliant, in particular
turning on RTTI support".


> Anyways, it's much easier to do it like:
>
> if(a == dynamic_cast<A*>(b)) ...

I find the simple downcasts more clear, and the cross-cast a bit challenging.

And apart from clarity, there is the academic problem that the cross-cast may
not be valid, e.g. in the case where a C contains two or more A sub-objects.

So I just provided the way that I found most clear, and guaranteed to work. :-)


Cheers & hth.,

- Alf

Notes:
[1] The special case casts in C++ include dynamic_cast<void*>, C style cast to
otherwise inaccessible base, reinterpret_cast to/from first data member of a
POD, and reinterpret_cast<char&> (he he...) to obtain address of object of silly
class that redefines address operator, but although the latter works in practice
I think that for non-POD it's formally UB or implementation defined behavior?

io_x

unread,
Nov 27, 2009, 2:58:37 AM11/27/09
to

"Danny Woods" <danny...@yahoo.co.uk> ha scritto nel messaggio
news:50skccr...@gmail.com...

>
> Hi all,
>
> Given this simple program:
>
> ----
>
> #include <cstdio>
>
> class A { public: virtual ~A() {} };
> class B { public: virtual ~B() {} };
> class C : public A, public B { public: virtual ~C() {} };
>
> int main(void)
> {
> C *c = new C();
> A *a = c;
> B *b = c;
>
> printf("c: %p; a: %p; b: %p\n", c, a, b);
>
> delete c;
> return 0;
> }
>
> ----
>
> Is it to be expected that the addresses stored in a and b are different?
> I've tried this with Visual C++ and Cygwin g++, with identical results.
>
> The problem I have is that there are other subclasses of A and B that
> are distinct, but that there's a special case where the combined
> subclass, C, is required to fill both roles. When the code that cleans
> up a and b runs later, I'll end up with double deletion unless I can

i not find in the example above no double free;
where is it?

Can i use "this" inside the distructor function?

------------------------------
#include <stdio.h>
#include <stdlib.h>
#define P printf
#define i8 signed char

class A{
public:
A(){ Aarr= (i8*) malloc(1024); }
virtual ~A()
{P("~A(); this=%p\n", this);
free(Aarr);
Aarr= (i8*) -1;
}
i8* Aarr;
};

class B{
public:
B(){ Barr= (i8*) malloc(1024); }
virtual ~B()
{P("~B(); this=%p\n", this);
free(Barr);
Barr= (i8*) -1;
}
i8* Barr;
};

class C : public A, public B{
public:

virtual ~C(){ P("~C(); this=%p\n", this); }
};

int main(void)
{ C *c = new C;


A *a = c;
B *b = c;

if(c->Aarr==0||c->Barr==0)
{P("No memory\n");
goto end;


}
printf("c: %p; a: %p; b: %p\n", c, a, b);

end:;
delete c;
P("END\n");
return 0;
}

------------------------------------
c: 00852FD4; a: 00852FD4; b: 00852FDC
~C(); this=00852FD4
~B(); this=00852FDC
~A(); this=00852FD4
END

io_x

unread,
Nov 28, 2009, 2:15:44 AM11/28/09
to

"io_x" <a...@b.c.invalid> ha scritto nel messaggio
news:4b0f84ec$0$10444$4faf...@reader2.news.tin.it...

here seems that both each of "delete a" and "delete c"
is like "delete c"

Thomas J. Gritzan

unread,
Nov 28, 2009, 12:04:04 PM11/28/09
to
io_x schrieb:

> "io_x" <a...@b.c.invalid> ha scritto nel messaggio
> news:4b0f84ec$0$10444$4faf...@reader2.news.tin.it...
>> #include <stdio.h>
>> #include <stdlib.h>
>> #define P printf
>> #define i8 signed char

Avoid macros in C++. Better would be:

typedef signed char i8;

Still better would be not to confuse the reader by hiding types and
functions behind 1- or 2-character names.

>> class A{
>> public:
>> A(){ Aarr= (i8*) malloc(1024); }

Avoid malloc/free in C++. std::vector<signed char> would be the C++ way.

>> virtual ~A()
>> {P("~A(); this=%p\n", this);
>> free(Aarr);
>> Aarr= (i8*) -1;

Any reason for this sentinel value? Why don't you use a NULL pointer
instead?

>> }
>> i8* Aarr;
>> };
>>
>> class B{
>> public:
>> B(){ Barr= (i8*) malloc(1024); }
>> virtual ~B()
>> {P("~B(); this=%p\n", this);
>> free(Barr);
>> Barr= (i8*) -1;
>> }
>> i8* Barr;
>> };
>>
>> class C : public A, public B{
>> public:
>> virtual ~C(){ P("~C(); this=%p\n", this); }
>> };
>>
>> int main(void)
>> { C *c = new C;
>> A *a = c;
>> B *b = c;
>>
>> if(c->Aarr==0||c->Barr==0)
>> {P("No memory\n");
>> goto end;
>> }
>> printf("c: %p; a: %p; b: %p\n", c, a, b);
>> end:;
>> delete c;
>
> here seems that both each of "delete a" and "delete c"
> is like "delete c"

Of course. The destructors are virtual. You can delete the object
through any pointer to a base class whose destructor is virtual.

--
Thomas

io_x

unread,
Nov 29, 2009, 3:50:09 AM11/29/09
to

"io_x" <a...@b.c.invalid> ha scritto nel messaggio
news:4b10cc58$0$34589$4faf...@reader1.news.tin.it...

>> int main(void)
>> { C *c = new C;
>> A *a = c;
>> B *b = c;
>>
>> if(c->Aarr==0||c->Barr==0)
>> {P("No memory\n");
>> goto end;
>> }
>> printf("c: %p; a: %p; b: %p\n", c, a, b);
>> end:;
>> delete c;
>
> here seems that both each of "delete a" and "delete c"
^^^^^^^^

"delete b"

io_x

unread,
Dec 1, 2009, 3:15:39 AM12/1/09
to

"io_x" <a...@b.c.invalid> ha scritto nel messaggio
news:4b0f84ec$0$10444$4faf...@reader2.news.tin.it...

>
> "Danny Woods" <danny...@yahoo.co.uk> ha scritto nel messaggio
> news:50skccr...@gmail.com...
>>
>> Hi all,
>>
>> Given this simple program:
>>
>> ----
>>
>> #include <cstdio>
>>
>> class A { public: virtual ~A() {} };
>> class B { public: virtual ~B() {} };
>> class C : public A, public B { public: virtual ~C() {} };
>>
>> int main(void)
>> {
>> C *c = new C();
>> A *a = c;
>> B *b = c;
>>
>> printf("c: %p; a: %p; b: %p\n", c, a, b);
>>
>> delete c;
>> return 0;
>> }
>>
>> ----
> i not find in the example above no double free;
> where is it?

where is the problem?
the debugger show that the call for construction
are C()A()B()
and for distruction ~C()~B()~A()
(this mean that printf print someting wrong here below)
where is the problem?

A()B()C()
c: 00852FC0; a: 00852FC0; b: 00852FC8
~C(); this=00852FC0
~B(); this=00852FC8
~A(); this=00852FC0
END

------------------------
#include <stdio.h>
#include <stdlib.h>
#define P printf
#define i8 signed char

#define V void

class A{
public:
A(){ Aarr= (i8*) malloc(1024); P("A()"); }
virtual ~A()
{P("~A(); this=%p\n", (V*)this);
free(Aarr);
Aarr=0;
}
i8* Aarr;
};

class B{
public:
B(){ Barr= (i8*) malloc(1024); P("B()"); }
virtual ~B()
{P("~B(); this=%p\n", (V*)this);
free(Barr);
Barr=0;
}
i8* Barr;
};

class C : public A, public B{
public:

C(){P("C()");}


virtual ~C(){ P("~C(); this=%p\n", this); }
};

int main(void)
{
C *c = new C;
A *a = c;
B *b = c;

if(c->Aarr==0||c->Barr==0)
{P("No memory\n");
goto end;
}

printf("\nc: %p; a: %p; b: %p\n", (V*)c, (V*)a, (V*)b);

Thomas J. Gritzan

unread,
Dec 1, 2009, 6:25:01 PM12/1/09
to
io_x schrieb:

> "io_x" <a...@b.c.invalid> ha scritto nel messaggio
> news:4b0f84ec$0$10444$4faf...@reader2.news.tin.it...
>> "Danny Woods" <danny...@yahoo.co.uk> ha scritto nel messaggio
>> news:50skccr...@gmail.com...
>>> Hi all,
>>>
>>> Given this simple program:
>>>
>>> ----
>>>
>>> #include <cstdio>
>>>
>>> class A { public: virtual ~A() {} };
>>> class B { public: virtual ~B() {} };
>>> class C : public A, public B { public: virtual ~C() {} };
>>>
>>> int main(void)
>>> {
>>> C *c = new C();
>>> A *a = c;
>>> B *b = c;
>>>
>>> printf("c: %p; a: %p; b: %p\n", c, a, b);
>>>
>>> delete c;
>>> return 0;
>>> }
>>>
>>> ----
>> i not find in the example above no double free;
>> where is it?

There's no double delete in this code. The double delete occured in
another usage/situation the OP described.

> where is the problem?
> the debugger show that the call for construction
> are C()A()B()
> and for distruction ~C()~B()~A()
> (this mean that printf print someting wrong here below)
> where is the problem?
>
> A()B()C()
> c: 00852FC0; a: 00852FC0; b: 00852FC8
> ~C(); this=00852FC0
> ~B(); this=00852FC8
> ~A(); this=00852FC0
> END

Your debugger is wrong. The base classes are constructed first, followed
by the derived class.

--
Thomas

Pete Becker

unread,
Dec 1, 2009, 8:16:19 PM12/1/09
to
Thomas J. Gritzan wrote:
> io_x schrieb:

>> the debugger show that the call for construction
>> are C()A()B()
>> and for distruction ~C()~B()~A()
>> (this mean that printf print someting wrong here below)
>> where is the problem?
>>
>> A()B()C()
>> c: 00852FC0; a: 00852FC0; b: 00852FC8
>> ~C(); this=00852FC0
>> ~B(); this=00852FC8
>> ~A(); this=00852FC0
>> END
>
> Your debugger is wrong. The base classes are constructed first, followed
> by the derived class.
>

The debugger isn't wrong. A C object is constructed by calling its
constructor. That constructor calls the constructors for its bases, A
then B, and then executes its own body. So if you set breakpoints on all
three constructors you'll break first at C(), then at A(), then at B().

--
Pete
Roundhouse Consulting, Ltd. (www.versatilecoding.com) Author of
"The Standard C++ Library Extensions: a Tutorial and Reference"
(www.petebecker.com/tr1book)

0 new messages