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.
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:
> 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.
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".
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 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.
o_O
Why???
How is that working???
--
Bolje je ziveti sto godina kao bogatun, nego jedan dan kao siromah!
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
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.
Not true. The destructors of A and B are virtual, so delete a; or delete
b; would be fine.
--
Thomas
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.)
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:
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?
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
here seems that both each of "delete a" and "delete c"
is like "delete c"
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
"delete b"
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);
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
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)