Discussion:
Unusual usage of IUknown
(too old to reply)
Gingko
2009-08-27 09:09:24 UTC
Permalink
Hello,

Please could someone tell me if it should be safe to derive a class from
IUknown, without any intent to use it as a COM object, but for the only
purpose of taking advantage of its reference count feature ?

I Would like to be able to use such classes with templates normally
associated with COM objects (using CComPtr smart pointers), but that I
sometimes also need with local class objects.

Best regards,

Gilles
Alex Blekhman
2009-08-27 11:35:47 UTC
Permalink
Post by Gingko
Please could someone tell me if it should be safe to derive a
class from IUknown, without any intent to use it as a COM
object, but for the only purpose of taking advantage of its
reference count feature ?
How can it be unsafe? IUknown declared as a regular struct. So,
you can derive from it as from any other C++ struct without any
problem.

Alex
Gingko
2009-08-27 13:55:31 UTC
Permalink
----- Original Message -----
From: "Alex Blekhman" <***@yahoo.com>
Newsgroups: microsoft.public.vc.language
Sent: Thursday, August 27, 2009 1:35 PM
Subject: Re: Unusual usage of IUknown
Post by Gingko
Please could someone tell me if it should be safe to derive a class from
IUknown, without any intent to use it as a COM object, but for the only
purpose of taking advantage of its reference count feature ?
How can it be unsafe? IUknown declared as a regular struct. So, you can
derive from it as from any other C++ struct without any problem.
Alex
Ok, thank you very much.

I was mainly wondering about the implementation of the (in that case) unused
QueryInterface member, and the fact of using the interface without defining
any GUID.

Gilles
Ben Voigt [C++ MVP]
2009-08-27 14:36:39 UTC
Permalink
Post by Gingko
----- Original Message -----
Newsgroups: microsoft.public.vc.language
Sent: Thursday, August 27, 2009 1:35 PM
Subject: Re: Unusual usage of IUknown
Post by Gingko
Please could someone tell me if it should be safe to derive a class
from IUknown, without any intent to use it as a COM object, but for
the only purpose of taking advantage of its reference count feature
?
How can it be unsafe? IUknown declared as a regular struct. So, you
can derive from it as from any other C++ struct without any problem.
Alex
Ok, thank you very much.
I was mainly wondering about the implementation of the (in that case)
unused QueryInterface member, and the fact of using the interface
without defining any GUID.
Gilles
Just implement QueryInterface so it rejects any GUID other than
IID_IUnknown. In case of a request for IUnknown, you'd do an AddRef of
course.
Stephan T. Lavavej [MSFT]
2009-08-28 02:07:31 UTC
Permalink
If you want reference counted ownership, you should use shared_ptr.
shared_ptr is nonintrusive, so you don't need to modify your class in order
to use it.

Stephan T. Lavavej
Visual C++ Libraries Developer
Post by Ben Voigt [C++ MVP]
Post by Gingko
----- Original Message -----
Newsgroups: microsoft.public.vc.language
Sent: Thursday, August 27, 2009 1:35 PM
Subject: Re: Unusual usage of IUknown
Post by Gingko
Please could someone tell me if it should be safe to derive a class
from IUknown, without any intent to use it as a COM object, but for
the only purpose of taking advantage of its reference count feature
?
How can it be unsafe? IUknown declared as a regular struct. So, you
can derive from it as from any other C++ struct without any problem.
Alex
Ok, thank you very much.
I was mainly wondering about the implementation of the (in that case)
unused QueryInterface member, and the fact of using the interface
without defining any GUID.
Gilles
Just implement QueryInterface so it rejects any GUID other than
IID_IUnknown. In case of a request for IUnknown, you'd do an AddRef of
course.
Carl Daniel [VC++ MVP]
2009-08-28 03:47:09 UTC
Permalink
Post by Stephan T. Lavavej [MSFT]
If you want reference counted ownership, you should use shared_ptr.
shared_ptr is nonintrusive, so you don't need to modify your class in
order to use it.
But test the performance first if you expect a lot of sharing and/or a lot
of creating/releasing of objects. In my tests (which were admittedly not
recent), I found the overhead of shared_ptr to be extreme (*) compared to an
intrusive reference count. YMMV. Your implementation might vary too - so
do the test yourself, don't just accept that your experience will match
mine.

-cd

(*) In one test of Boost Spirit, which makes extensive use of shared_ptr, I
found over 80% of the execution time of the program was spent in the
shared_ptr code. I'm sure this was an extreme case, but test for yourself
to be sure.
Gingko
2009-08-28 19:08:03 UTC
Permalink
----- Original Message -----
From: "Carl Daniel [VC++ MVP]"
<***@mvps.org.nospam>
Newsgroups: microsoft.public.vc.language
Sent: Friday, August 28, 2009 5:47 AM
Subject: Re: Unusual usage of IUknown
Post by Carl Daniel [VC++ MVP]
(*) In one test of Boost Spirit, which makes extensive use of shared_ptr,
I found over 80% of the execution time of the program was spent in the
shared_ptr code. I'm sure this was an extreme case, but test for yourself
to be sure.
I don't understand.

I thought that shared pointeres were mostly invented for fitting the need of
being able to take a unlimited copy of a pointer to an object without having
to worry about which of these pointers have ownership and for knowing when
the object will be free for deletion.

I thought also that - among other reasons - this avoid the basic
alternative of copying the object again and again like it is commonly done
if you want to get many copies of an object allocated in memory, which can
be very time and memory consuming if the object is very big.

Where is the interest of using shared pointers if they can be maybe more
time consuming by themselves than copying a pointed object again and again ?

Gilles
Stephan T. Lavavej [MSFT]
2009-08-28 21:49:50 UTC
Permalink
VC9 SP1 contains optimizations to avoid incrementing and decrementing
shared_ptr's refcount (e.g. when reallocating a vector<shared_ptr<T>>).
VC10's move semantics extend this even further. Also, VC10's
make_shared<T>() eliminates the overhead of having two dynamic memory
allocations; it's able to put the object and its reference count control
block in the same chunk of memory, which gives you efficiency very close to
that of intrusive reference counting.

Of course, shared_ptr uses interlocked operations for threadsafe
refcounting. Naive intrusive refcounts that use ordinary increments and
decrements will obviously be faster, but restricts ownership to a single
thread.

STL
Post by Carl Daniel [VC++ MVP]
Post by Stephan T. Lavavej [MSFT]
If you want reference counted ownership, you should use shared_ptr.
shared_ptr is nonintrusive, so you don't need to modify your class in
order to use it.
But test the performance first if you expect a lot of sharing and/or a lot
of creating/releasing of objects. In my tests (which were admittedly not
recent), I found the overhead of shared_ptr to be extreme (*) compared to
an intrusive reference count. YMMV. Your implementation might vary too -
so do the test yourself, don't just accept that your experience will match
mine.
-cd
(*) In one test of Boost Spirit, which makes extensive use of shared_ptr,
I found over 80% of the execution time of the program was spent in the
shared_ptr code. I'm sure this was an extreme case, but test for yourself
to be sure.
Carl Daniel [VC++ MVP]
2009-08-29 14:15:16 UTC
Permalink
Post by Stephan T. Lavavej [MSFT]
VC9 SP1 contains optimizations to avoid incrementing and decrementing
shared_ptr's refcount (e.g. when reallocating a
vector<shared_ptr<T>>). VC10's move semantics extend this even
further. Also, VC10's make_shared<T>() eliminates the overhead of
having two dynamic memory allocations; it's able to put the object
and its reference count control block in the same chunk of memory,
which gives you efficiency very close to that of intrusive reference
counting.
I'm sure the move semantics in VC10 would have made my Spirit test go MUCH
faster. A very large portion of the overhead that I measured was from
copying of shared_ptr's.
Post by Stephan T. Lavavej [MSFT]
Of course, shared_ptr uses interlocked operations for threadsafe
refcounting. Naive intrusive refcounts that use ordinary increments
and decrements will obviously be faster, but restricts ownership to a
single thread.
Naturally!

At the time, I wanted a shared_ptr that gave me the choice of whether I
wanted thread safety or not. I'm still on the fence as to whether this
would be a good option - I'm sure it would be abused by many naive users
looking for unneeded optimizations and instead creating concurrency problems
that would be had to track down.
Post by Stephan T. Lavavej [MSFT]
STL
-cd
Gingko
2009-08-29 21:08:54 UTC
Permalink
Post by Stephan T. Lavavej [MSFT]
VC9 SP1 contains optimizations to avoid incrementing and decrementing
shared_ptr's refcount (e.g. when reallocating a vector<shared_ptr<T>>).
VC10's move semantics extend this even further. Also, VC10's
make_shared<T>() eliminates the overhead of having two dynamic memory
allocations; it's able to put the object and its reference count control
block in the same chunk of memory, which gives you efficiency very close
to that of intrusive reference counting.
I use mainly VC8 SP1 (VS2005) right now. This is a choice (too much
stability problems with VC9, even with SP1, and no changes really useful for
me ; also VC8 is more commonly shared with other members of my projects).
Post by Stephan T. Lavavej [MSFT]
Of course, shared_ptr uses interlocked operations for threadsafe
refcounting. Naive intrusive refcounts that use ordinary increments and
decrements will obviously be faster, but restricts ownership to a single
thread.
This was not really the question that I asked and my code was only a quick
first run for testing this kind of feature, but anyway, thank you for
pointing this out : I'm aware of this race condition problem, but I don't
know it very well.

You seem to know a lot about this subject, maybe you will be able to help me
on this matter also ?
I modified my code for using Interlocked functions (see at bottom).
But I'm still not sure that it is very multithread safe : another thread can
probably change the reference count between InterlockedCompareExchange and
InterlockedDecrement in my Release function. How can I be sure to avoid this
problem ?

About performance questions : I looked at the assembly code, and I verified
that the actual assembly code for these Interlocked functions is only about
5 or 6 machine instructions, like this:

***@4:
7C809806 8B 4C 24 04 mov ecx,dword ptr [esp+4]
7C80980A B8 01 00 00 00 mov eax,1
7C80980F F0 0F C1 01 lock xadd dword ptr [ecx],eax
7C809813 40 inc eax
7C809814 C2 04 00 ret 4

Even considering that there can be (probably quite rarely) some wait times
because of locked states from other threads, do you really think that I can
have significant overhead problems with that ?

Gilles

-----------------------------------------------------------------------------------------------------------


/** Minimal \p IUnknown implementation for being able to only take advantage
of its
* reference count feature and to allow building smart pointers using \p
CComPtr
**/
class CUnknownStub : public IUnknown
{
volatile ULONG m_cRef; //!< Reference counter
public:
/// Minimal interface query
virtual STDMETHODIMP QueryInterface(REFIID riid, void **ppv);

/// Minimal reference increment implementation
virtual ULONG STDMETHODCALLTYPE AddRef() {
return InterlockedIncrement(reinterpret_cast<volatile LONG
*>(&m_cRef)); }

/// Minimal reference decrement implementation (with object destruction
when reaching zero)
virtual ULONG STDMETHODCALLTYPE Release();

/// Constructor
CUnknownStub() :
m_cRef(0) // initialize reference count
{}

protected:
/// Destructor (protected because it must never be externally called)
virtual ~CUnknownStub() {
/* empty */ }
};


/// Minimal interface query
STDMETHODIMP CUnknownStub::QueryInterface(REFIID riid, void **ppv)
{
if (!ppv)
return E_POINTER;
if ((riid == IID_IUnknown)) {
*ppv = (LPVOID) this;
AddRef(); // AddRef
return S_OK;
}
*ppv = NULL;
return E_NOINTERFACE;
}

#define DESTRUCTOR_REFCOUNT MAXLONG

/// Minimal reference decrement implementation (with object destruction when
reaching zero)
ULONG STDMETHODCALLTYPE CUnknownStub::Release()
{
if (
InterlockedCompareExchange(
reinterpret_cast<volatile LONG *>(&m_cRef), DESTRUCTOR_REFCOUNT,
1) == 1
) {
delete this;
return 0;
}
return InterlockedDecrement(reinterpret_cast<volatile LONG *>(&m_cRef));
}
Gingko
2009-08-30 06:31:13 UTC
Permalink
Post by Gingko
/// Minimal reference decrement implementation (with object destruction
when reaching zero)
ULONG STDMETHODCALLTYPE CUnknownStub::Release()
{
if (
InterlockedCompareExchange(
reinterpret_cast<volatile LONG *>(&m_cRef),
DESTRUCTOR_REFCOUNT, 1) == 1
) {
delete this;
return 0;
}
return InterlockedDecrement(reinterpret_cast<volatile LONG
*>(&m_cRef));
}
Ok, maybe I made too much there.
I have been a little puzzled by the content of this page :
http://blogs.msdn.com/oldnewthing/archive/2005/09/28/474855.aspx

Probably I should write only this :
ULONG STDMETHODCALLTYPE CUnknownStub::Release(){ LONG cRef =
InterlockedDecrement(reinterpret_cast<volatile LONG *>(&m_cRef)); if
(cRef == 0) { m_cRef = DESTRUCTOR_REFCOUNT; // optional ?
delete this; } return (ULONG)cRef;}Gilles
Gingko
2009-08-28 18:27:20 UTC
Permalink
----- Original Message -----
From: "Stephan T. Lavavej [MSFT]" <***@microsoft.com>
Newsgroups: microsoft.public.vc.language
Sent: Friday, August 28, 2009 4:07 AM
Subject: Re: Unusual usage of IUknown
Post by Stephan T. Lavavej [MSFT]
If you want reference counted ownership, you should use shared_ptr.
shared_ptr is nonintrusive, so you don't need to modify your class in
order to use it.
Post by Stephan T. Lavavej [MSFT]
If you want reference counted ownership, you should use shared_ptr.
shared_ptr is nonintrusive, so you don't need to modify your class in
order to use it.
But test the performance first if you expect a lot of sharing and/or a lot
of creating/releasing of objects. In my tests (which were admittedly not
recent), I found the overhead of shared_ptr to be extreme (*) compared to
an intrusive reference count. YMMV. Your implementation might vary too -
so do the test yourself, don't just accept that your experience will match
mine.
-cd
(*) In one test of Boost Spirit, which makes extensive use of shared_ptr,
I found over 80% of the execution time of the program was spent in the
shared_ptr code. I'm sure this was an extreme case, but test for yourself
to be sure.
I understand, and I already thought to it, but "shared_ptr" is not part of
STL nor ATL library, it is part of Boost library which is not a standard
one.

So it means that, for this only "shared_ptr" need, I would have to add at
least a part of this Boost library to my project (which is a shared
projet), I think this is a step a little big unless I or we decide to use
much more of the Boost library.

Also, using IUknnown has the advantage to have some compatibility with real
COM objects that also part of the same projet, allowing to manage both of
them with the same templates.

Right now I wrote the code quoted at the end of this message.

After that, I just declare my CUnknownStub as a base class to the classes
for which I need reference count.

Of course this is a little more intrusive, but I think it is rather simple
and that it shouldn't have a lot of overhead.

Gilles Reeves

--------------------------------------------------------------------------------------------------------------------------

/** Minimal \p IUnknown implementation for being able to only take advantage
of its
* reference count feature and to allow building smart pointers using \p
CComPtr
**/
class CUnknownStub : public IUnknown
{
ULONG m_cRef; //!< Reference counter
public:
/// Minimal interface query
virtual STDMETHODIMP QueryInterface(REFIID riid, void **ppv);

/// Minimal reference increment implementation
virtual ULONG STDMETHODCALLTYPE AddRef() {
return ++m_cRef; }

/// Minimal reference decrement implementation (with object destruction
when reaching zero)
virtual ULONG STDMETHODCALLTYPE Release();

/// Constructor
CUnknownStub() :
m_cRef(0) // initialize reference count
{}

/// Destructor
virtual ~CUnknownStub() {
/* empty */ }
};


/// Minimal interface query
STDMETHODIMP CUnknownStub::QueryInterface(REFIID riid, void **ppv)
{
if (!ppv)
return E_POINTER;
if ((riid == IID_IUnknown)) {
*ppv = (LPVOID) this;
++m_cRef; // AddRef
return S_OK;
}
*ppv = NULL;
return E_NOINTERFACE;
}

/// Minimal reference decrement implementation (with object destruction when
reaching zero)
ULONG STDMETHODCALLTYPE CUnknownStub::Release()
{
if (--m_cRef == 0) {
delete this;
return 0; // "this" have been deleted, so don't return
this->m_cRef
}
return m_cRef;
}
Stephan T. Lavavej [MSFT]
2009-08-28 21:44:27 UTC
Permalink
Post by Gingko
I understand, and I already thought to it, but "shared_ptr" is not part of
STL nor ATL library, it is part of Boost library which is not a standard
one.
(ATL is a Microsoft-specific library.)

shared_ptr was originally implemented by Boost, but then was incorporated
into the TR1 extensions to the C++98/03 Standard Library (as
std::tr1::shared_ptr), and then integrated into the C++0x Standard Library
(as std::shared_ptr).

In VC9 SP1, if you include <memory>, std::tr1::shared_ptr is available. In
VC10, it's still available as std::tr1::shared_ptr for backwards
compatibility, but it's also available as std::shared_ptr.

STL
Post by Gingko
----- Original Message -----
Newsgroups: microsoft.public.vc.language
Sent: Friday, August 28, 2009 4:07 AM
Subject: Re: Unusual usage of IUknown
Post by Stephan T. Lavavej [MSFT]
If you want reference counted ownership, you should use shared_ptr.
shared_ptr is nonintrusive, so you don't need to modify your class in
order to use it.
Post by Stephan T. Lavavej [MSFT]
If you want reference counted ownership, you should use shared_ptr.
shared_ptr is nonintrusive, so you don't need to modify your class in
order to use it.
But test the performance first if you expect a lot of sharing and/or a
lot of creating/releasing of objects. In my tests (which were admittedly
not recent), I found the overhead of shared_ptr to be extreme (*)
compared to an intrusive reference count. YMMV. Your implementation
might vary too - so do the test yourself, don't just accept that your
experience will match mine.
-cd
(*) In one test of Boost Spirit, which makes extensive use of shared_ptr,
I found over 80% of the execution time of the program was spent in the
shared_ptr code. I'm sure this was an extreme case, but test for
yourself to be sure.
I understand, and I already thought to it, but "shared_ptr" is not part of
STL nor ATL library, it is part of Boost library which is not a standard
one.
So it means that, for this only "shared_ptr" need, I would have to add at
least a part of this Boost library to my project (which is a shared
projet), I think this is a step a little big unless I or we decide to use
much more of the Boost library.
Also, using IUknnown has the advantage to have some compatibility with
real COM objects that also part of the same projet, allowing to manage
both of them with the same templates.
Right now I wrote the code quoted at the end of this message.
After that, I just declare my CUnknownStub as a base class to the classes
for which I need reference count.
Of course this is a little more intrusive, but I think it is rather simple
and that it shouldn't have a lot of overhead.
Gilles Reeves
--------------------------------------------------------------------------------------------------------------------------
/** Minimal \p IUnknown implementation for being able to only take
advantage of its
* reference count feature and to allow building smart pointers using \p
CComPtr
**/
class CUnknownStub : public IUnknown
{
ULONG m_cRef; //!< Reference counter
/// Minimal interface query
virtual STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
/// Minimal reference increment implementation
virtual ULONG STDMETHODCALLTYPE AddRef() {
return ++m_cRef; }
/// Minimal reference decrement implementation (with object destruction
when reaching zero)
virtual ULONG STDMETHODCALLTYPE Release();
/// Constructor
m_cRef(0) // initialize reference count
{}
/// Destructor
virtual ~CUnknownStub() {
/* empty */ }
};
/// Minimal interface query
STDMETHODIMP CUnknownStub::QueryInterface(REFIID riid, void **ppv)
{
if (!ppv)
return E_POINTER;
if ((riid == IID_IUnknown)) {
*ppv = (LPVOID) this;
++m_cRef; // AddRef
return S_OK;
}
*ppv = NULL;
return E_NOINTERFACE;
}
/// Minimal reference decrement implementation (with object destruction
when reaching zero)
ULONG STDMETHODCALLTYPE CUnknownStub::Release()
{
if (--m_cRef == 0) {
delete this;
return 0; // "this" have been deleted, so don't return
this->m_cRef
}
return m_cRef;
}
Ben Voigt [C++ MVP]
2009-08-31 21:49:59 UTC
Permalink
Post by Gingko
/// Minimal reference decrement implementation (with object destruction when
reaching zero)
ULONG STDMETHODCALLTYPE CUnknownStub::Release()
{
if (--m_cRef == 0) {
delete this;
This line worries me in a base class. Is it guaranteed that this is the
same pointer returned from new, which should also be passed to delete? Or
could "this" represent a sub-object at (potentially) a different address?
It's probably only a problem if this isn't the leftmost base class at every
level, but COM usually does use multiple inheritance. Just a trap for the
unwary.
Post by Gingko
return 0; // "this" have been deleted, so don't return
this->m_cRef
}
return m_cRef;
}
Gingko
2009-09-01 06:05:43 UTC
Permalink
Post by Ben Voigt [C++ MVP]
Post by Gingko
/// Minimal reference decrement implementation (with object destruction when
reaching zero)
ULONG STDMETHODCALLTYPE CUnknownStub::Release()
{
if (--m_cRef == 0) {
delete this;
This line worries me in a base class. Is it guaranteed that this is the
same pointer returned from new, which should also be passed to delete? Or
could "this" represent a sub-object at (potentially) a different address?
It's probably only a problem if this isn't the leftmost base class at
every level, but COM usually does use multiple inheritance. Just a trap
for the unwary.
Post by Gingko
return 0; // "this" have been deleted, so don't return
this->m_cRef
}
return m_cRef;
}
I thought that using a virtual destructor was the mean to avoid this problem
?

Was I wrong ?

Gilles
Scot T Brennecke
2009-09-01 06:41:17 UTC
Permalink
Post by Gingko
Post by Ben Voigt [C++ MVP]
Post by Gingko
/// Minimal reference decrement implementation (with object destruction when
reaching zero)
ULONG STDMETHODCALLTYPE CUnknownStub::Release()
{
if (--m_cRef == 0) {
delete this;
This line worries me in a base class. Is it guaranteed that this is the
same pointer returned from new, which should also be passed to delete? Or
could "this" represent a sub-object at (potentially) a different address?
It's probably only a problem if this isn't the leftmost base class at
every level, but COM usually does use multiple inheritance. Just a trap
for the unwary.
Post by Gingko
return 0; // "this" have been deleted, so don't return
this->m_cRef
}
return m_cRef;
}
I thought that using a virtual destructor was the mean to avoid this problem
?
Was I wrong ?
Gilles
A virtual destructor only helps to make sure the most-derived destructor is actually called. If this is a sub-object aggregated
into another object, you've still got big trouble.
Gingko
2009-09-01 11:10:37 UTC
Permalink
Post by Scot T Brennecke
A virtual destructor only helps to make sure the most-derived destructor
is actually called. If this is a sub-object aggregated into another
object, you've still got big trouble.
Ok.

I juste made a small test for checking that again (see below).
Whatever "this" pointer I use for deletion, the result is the same, and all
destructors are called in the same order (which is the reverse of
construction order).

But maybe I didn't make there a proper "sub-object aggregated into another
object" ?
Of course I will never declare such objects as flat members of other objects
(if needed, I'll use pointers to these objects, likely protected by
CComPtr).

Please could you either modify my code or provide a sample in order to
illustrate what you are saying ?

Gilles


------------------------------------------------


#include <iostream>

using namespace std;

// Just for easily checking what happens if you remove "virtual" in front of
destructors
#define VIRTUAL virtual

class V
{
char * szData;
public:
V() : szData("V") {
cout << "calling V constructor, szData=" << szData << endl;
}
V * get_V_this() {
return this;}
VIRTUAL ~V() {
cout << "calling V destructor, szData=" << szData << endl;
}
};

class W : public virtual V
{
char * szData;
public:
W() : szData("W") {
cout << "calling W constructor, szData=" << szData << endl;
}
W * get_W_this() {
return this;}
VIRTUAL ~W() {
cout << "calling W destructor, szData=" << szData << endl;
}
};

class X : public virtual V
{
char * szData;
public:
X() : szData("X") {
cout << "calling X constructor, szData=" << szData << endl;
}
X * get_X_this() {
return this;}
VIRTUAL ~X() {
cout << "calling X destructor, szData=" << szData << endl;
}
};

class Y
{
char * szData;
public:
Y() : szData("Y") {
cout << "calling Y constructor, szData=" << szData << endl;
}
Y * get_Y_this() {
return this;}
VIRTUAL ~Y() {
cout << "calling Y destructor, szData=" << szData << endl;
}
};


class Z
{
char * szData;
public:
Z() : szData("Z") {
cout << "calling Z constructor, szData=" << szData << endl;
}
Z * get_Z_this() {
return this;}
VIRTUAL ~Z() {
cout << "calling Z destructor, szData=" << szData << endl;
}
};

class B : public W, public X, public Y
{
char * szData;
public:
B() : szData("B") {
cout << "calling B constructor, szData=" << szData << endl;
}
B * get_B_this() {
return this;}
VIRTUAL ~B() {
cout << "calling B destructor, szData=" << szData << endl;
}
};

class A : public Z, public B
{
char * szData;
public:
A() : szData("A") {
cout << "calling A constructor, szData=" << szData << endl;
}
VIRTUAL ~A() {
cout << "calling A destructor, szData=" << szData << endl;
}
};

int main(int argc, char * argv[])
{
A * ptA;

ptA = new A;
cout << "*** deleting A instance by A::this = 0x" << hex << ptA << endl;
delete ptA;
cout << "-----------------" << endl;
ptA = new A;
B * ptB = ptA->get_B_this();
cout << "*** deleting A instance by B::this = 0x" << hex << ptB << endl;
delete ptB;
cout << "-----------------" << endl;
ptA = new A;
V * ptV = ptA->get_V_this();
cout << "*** deleting A instance by V::this = 0x" << hex << ptV << endl;
delete ptV;
cout << "-----------------" << endl;
ptA = new A;
W * ptW = ptA->get_W_this();
cout << "*** deleting A instance by W::this = 0x" << hex << ptW << endl;
delete ptW;
cout << "-----------------" << endl;
ptA = new A;
X * ptX = ptA->get_X_this();
cout << "*** deleting A instance by X::this = 0x" << hex << ptX << endl;
delete ptX;
cout << "-----------------" << endl;
ptA = new A;
Y * ptY = ptA->get_Y_this();
cout << "*** deleting A instance by Y::this = 0x" << hex << ptY << endl;
delete ptY;
cout << "-----------------" << endl;
ptA = new A;
Z * ptZ = ptA->get_Z_this();
cout << "*** deleting A instance by Z::this = 0x" << hex << ptZ << endl;
delete ptZ;
return 0;
}


------------------------------------------------

Output when running the above code :

calling V constructor, szData=V
calling Z constructor, szData=Z
calling W constructor, szData=W
calling X constructor, szData=X
calling Y constructor, szData=Y
calling B constructor, szData=B
calling A constructor, szData=A
*** deleting A instance by A::this = 0x00367178
calling A destructor, szData=A
calling B destructor, szData=B
calling Y destructor, szData=Y
calling X destructor, szData=X
calling W destructor, szData=W
calling Z destructor, szData=Z
calling V destructor, szData=V
-----------------
calling V constructor, szData=V
calling Z constructor, szData=Z
calling W constructor, szData=W
calling X constructor, szData=X
calling Y constructor, szData=Y
calling B constructor, szData=B
calling A constructor, szData=A
*** deleting A instance by B::this = 0x00367180
calling A destructor, szData=A
calling B destructor, szData=B
calling Y destructor, szData=Y
calling X destructor, szData=X
calling W destructor, szData=W
calling Z destructor, szData=Z
calling V destructor, szData=V
-----------------
calling V constructor, szData=V
calling Z constructor, szData=Z
calling W constructor, szData=W
calling X constructor, szData=X
calling Y constructor, szData=Y
calling B constructor, szData=B
calling A constructor, szData=A
*** deleting A instance by V::this = 0x003671A0
calling A destructor, szData=A
calling B destructor, szData=B
calling Y destructor, szData=Y
calling X destructor, szData=X
calling W destructor, szData=W
calling Z destructor, szData=Z
calling V destructor, szData=V
-----------------
calling V constructor, szData=V
calling Z constructor, szData=Z
calling W constructor, szData=W
calling X constructor, szData=X
calling Y constructor, szData=Y
calling B constructor, szData=B
calling A constructor, szData=A
*** deleting A instance by W::this = 0x00367188
calling A destructor, szData=A
calling B destructor, szData=B
calling Y destructor, szData=Y
calling X destructor, szData=X
calling W destructor, szData=W
calling Z destructor, szData=Z
calling V destructor, szData=V
-----------------
calling V constructor, szData=V
calling Z constructor, szData=Z
calling W constructor, szData=W
calling X constructor, szData=X
calling Y constructor, szData=Y
calling B constructor, szData=B
calling A constructor, szData=A
*** deleting A instance by X::this = 0x00367190
calling A destructor, szData=A
calling B destructor, szData=B
calling Y destructor, szData=Y
calling X destructor, szData=X
calling W destructor, szData=W
calling Z destructor, szData=Z
calling V destructor, szData=V
-----------------
calling V constructor, szData=V
calling Z constructor, szData=Z
calling W constructor, szData=W
calling X constructor, szData=X
calling Y constructor, szData=Y
calling B constructor, szData=B
calling A constructor, szData=A
*** deleting A instance by Y::this = 0x00367180
calling A destructor, szData=A
calling B destructor, szData=B
calling Y destructor, szData=Y
calling X destructor, szData=X
calling W destructor, szData=W
calling Z destructor, szData=Z
calling V destructor, szData=V
-----------------
calling V constructor, szData=V
calling Z constructor, szData=Z
calling W constructor, szData=W
calling X constructor, szData=X
calling Y constructor, szData=Y
calling B constructor, szData=B
calling A constructor, szData=A
*** deleting A instance by Z::this = 0x00367178
calling A destructor, szData=A
calling B destructor, szData=B
calling Y destructor, szData=Y
calling X destructor, szData=X
calling W destructor, szData=W
calling Z destructor, szData=Z
calling V destructor, szData=V
Scot T Brennecke
2009-09-02 03:43:24 UTC
Permalink
Post by Gingko
Post by Scot T Brennecke
A virtual destructor only helps to make sure the most-derived destructor
is actually called. If this is a sub-object aggregated into another
object, you've still got big trouble.
Ok.
I juste made a small test for checking that again (see below).
Whatever "this" pointer I use for deletion, the result is the same, and all
destructors are called in the same order (which is the reverse of
construction order).
But maybe I didn't make there a proper "sub-object aggregated into another
object" ?
Of course I will never declare such objects as flat members of other objects
(if needed, I'll use pointers to these objects, likely protected by
CComPtr).
Please could you either modify my code or provide a sample in order to
illustrate what you are saying ?
Gilles
------------------------------------------------
#include <iostream>
using namespace std;
// Just for easily checking what happens if you remove "virtual" in front of
destructors
#define VIRTUAL virtual
class V
{
char * szData;
V() : szData("V") {
cout << "calling V constructor, szData=" << szData << endl;
}
V * get_V_this() {
return this;}
VIRTUAL ~V() {
cout << "calling V destructor, szData=" << szData << endl;
}
};
class W : public virtual V
{
char * szData;
W() : szData("W") {
cout << "calling W constructor, szData=" << szData << endl;
}
W * get_W_this() {
return this;}
VIRTUAL ~W() {
cout << "calling W destructor, szData=" << szData << endl;
}
};
class X : public virtual V
{
char * szData;
X() : szData("X") {
cout << "calling X constructor, szData=" << szData << endl;
}
X * get_X_this() {
return this;}
VIRTUAL ~X() {
cout << "calling X destructor, szData=" << szData << endl;
}
};
class Y
{
char * szData;
Y() : szData("Y") {
cout << "calling Y constructor, szData=" << szData << endl;
}
Y * get_Y_this() {
return this;}
VIRTUAL ~Y() {
cout << "calling Y destructor, szData=" << szData << endl;
}
};
class Z
{
char * szData;
Z() : szData("Z") {
cout << "calling Z constructor, szData=" << szData << endl;
}
Z * get_Z_this() {
return this;}
VIRTUAL ~Z() {
cout << "calling Z destructor, szData=" << szData << endl;
}
};
class B : public W, public X, public Y
{
char * szData;
B() : szData("B") {
cout << "calling B constructor, szData=" << szData << endl;
}
B * get_B_this() {
return this;}
VIRTUAL ~B() {
cout << "calling B destructor, szData=" << szData << endl;
}
};
class A : public Z, public B
{
char * szData;
A() : szData("A") {
cout << "calling A constructor, szData=" << szData << endl;
}
VIRTUAL ~A() {
cout << "calling A destructor, szData=" << szData << endl;
}
};
int main(int argc, char * argv[])
{
A * ptA;
ptA = new A;
cout << "*** deleting A instance by A::this = 0x" << hex << ptA << endl;
delete ptA;
cout << "-----------------" << endl;
ptA = new A;
B * ptB = ptA->get_B_this();
cout << "*** deleting A instance by B::this = 0x" << hex << ptB << endl;
delete ptB;
cout << "-----------------" << endl;
ptA = new A;
V * ptV = ptA->get_V_this();
cout << "*** deleting A instance by V::this = 0x" << hex << ptV << endl;
delete ptV;
cout << "-----------------" << endl;
ptA = new A;
W * ptW = ptA->get_W_this();
cout << "*** deleting A instance by W::this = 0x" << hex << ptW << endl;
delete ptW;
cout << "-----------------" << endl;
ptA = new A;
X * ptX = ptA->get_X_this();
cout << "*** deleting A instance by X::this = 0x" << hex << ptX << endl;
delete ptX;
cout << "-----------------" << endl;
ptA = new A;
Y * ptY = ptA->get_Y_this();
cout << "*** deleting A instance by Y::this = 0x" << hex << ptY << endl;
delete ptY;
cout << "-----------------" << endl;
ptA = new A;
Z * ptZ = ptA->get_Z_this();
cout << "*** deleting A instance by Z::this = 0x" << hex << ptZ << endl;
delete ptZ;
return 0;
}
------------------------------------------------
calling V constructor, szData=V
calling Z constructor, szData=Z
calling W constructor, szData=W
calling X constructor, szData=X
calling Y constructor, szData=Y
calling B constructor, szData=B
calling A constructor, szData=A
*** deleting A instance by A::this = 0x00367178
calling A destructor, szData=A
calling B destructor, szData=B
calling Y destructor, szData=Y
calling X destructor, szData=X
calling W destructor, szData=W
calling Z destructor, szData=Z
calling V destructor, szData=V
-----------------
calling V constructor, szData=V
calling Z constructor, szData=Z
calling W constructor, szData=W
calling X constructor, szData=X
calling Y constructor, szData=Y
calling B constructor, szData=B
calling A constructor, szData=A
*** deleting A instance by B::this = 0x00367180
calling A destructor, szData=A
calling B destructor, szData=B
calling Y destructor, szData=Y
calling X destructor, szData=X
calling W destructor, szData=W
calling Z destructor, szData=Z
calling V destructor, szData=V
-----------------
calling V constructor, szData=V
calling Z constructor, szData=Z
calling W constructor, szData=W
calling X constructor, szData=X
calling Y constructor, szData=Y
calling B constructor, szData=B
calling A constructor, szData=A
*** deleting A instance by V::this = 0x003671A0
calling A destructor, szData=A
calling B destructor, szData=B
calling Y destructor, szData=Y
calling X destructor, szData=X
calling W destructor, szData=W
calling Z destructor, szData=Z
calling V destructor, szData=V
-----------------
calling V constructor, szData=V
calling Z constructor, szData=Z
calling W constructor, szData=W
calling X constructor, szData=X
calling Y constructor, szData=Y
calling B constructor, szData=B
calling A constructor, szData=A
*** deleting A instance by W::this = 0x00367188
calling A destructor, szData=A
calling B destructor, szData=B
calling Y destructor, szData=Y
calling X destructor, szData=X
calling W destructor, szData=W
calling Z destructor, szData=Z
calling V destructor, szData=V
-----------------
calling V constructor, szData=V
calling Z constructor, szData=Z
calling W constructor, szData=W
calling X constructor, szData=X
calling Y constructor, szData=Y
calling B constructor, szData=B
calling A constructor, szData=A
*** deleting A instance by X::this = 0x00367190
calling A destructor, szData=A
calling B destructor, szData=B
calling Y destructor, szData=Y
calling X destructor, szData=X
calling W destructor, szData=W
calling Z destructor, szData=Z
calling V destructor, szData=V
-----------------
calling V constructor, szData=V
calling Z constructor, szData=Z
calling W constructor, szData=W
calling X constructor, szData=X
calling Y constructor, szData=Y
calling B constructor, szData=B
calling A constructor, szData=A
*** deleting A instance by Y::this = 0x00367180
calling A destructor, szData=A
calling B destructor, szData=B
calling Y destructor, szData=Y
calling X destructor, szData=X
calling W destructor, szData=W
calling Z destructor, szData=Z
calling V destructor, szData=V
-----------------
calling V constructor, szData=V
calling Z constructor, szData=Z
calling W constructor, szData=W
calling X constructor, szData=X
calling Y constructor, szData=Y
calling B constructor, szData=B
calling A constructor, szData=A
*** deleting A instance by Z::this = 0x00367178
calling A destructor, szData=A
calling B destructor, szData=B
calling Y destructor, szData=Y
calling X destructor, szData=X
calling W destructor, szData=W
calling Z destructor, szData=Z
calling V destructor, szData=V
All your examples do not exhibit any aggregation. I think you understand the potential problem if you had

class V
{
char * szData;
ULONG m_cRef;
public:
V() : szData("V") {}
VIRTUAL ~V() {}
ULONG STDMETHODCALLTYPE CUnknownStub::Release()
{
if (--m_cRef == 0) {
delete this;
return 0;
}
return m_cRef;
}
};

class W
{
V v;
char * szData;
public:
W() : szData("W") {}
VIRTUAL ~W() {}
};
Gingko
2009-09-02 06:54:35 UTC
Permalink
Post by Scot T Brennecke
All your examples do not exhibit any aggregation. I think you understand
the potential problem if you had
class V
{
char * szData;
ULONG m_cRef;
V() : szData("V") {}
VIRTUAL ~V() {}
ULONG STDMETHODCALLTYPE CUnknownStub::Release()
{
if (--m_cRef == 0) {
delete this;
return 0;
}
return m_cRef;
}
};
class W
{
V v;
char * szData;
W() : szData("W") {}
VIRTUAL ~W() {}
};
This is completely against the underlying idea of my "CUnknownStub" class
Post by Scot T Brennecke
Post by Gingko
Of course I will never declare such objects as flat members of other
objects (if needed, I'll use pointers to these objects, likely protected
by CComPtr).
If I had to build it that way, I'd write something like :

class W
{
CComPtr<V> v;
char * szData;
public:
W() :
v(new V),
szData("W")
{
}
VIRTUAL ~W() {}
};

... although, thinking to it better, even if I write it like you did, this
should actually not raise any problem :

As no member of my initial "CUnknownStub" class are implied in its
constructor nor in its destructor, these members (including "Release" and
its "delete" instruction) would be simply ignored if I aggregate it, leaving
only possible class duplication problems, if ever this can be a problem, and
the fact that nobody should try to call its "Release" member from the
outside, but who would ever think to do that ? :-)

And of course, I would never try to use "delete" on the "this" member of the
"v" aggregate, as well as I would never try to use "delete" on any object
not created by "new".

Anyway, the idea of the "CUnknownStub" class is to emulate the reference
count feature of the COM objects, so normally it is expected to not be
aggregated.

Normal "COM" objects are expected to not be aggregated as well.

So I still don't understand why this question has even been raised ...

Gilles
Gingko
2009-09-02 07:18:56 UTC
Permalink
Post by Gingko
This is completely against the underlying idea of my "CUnknownStub" class
[..]
... and sorry, as we are more or less mixing samples in this thread, I
forgot to mention that both V and W classes should inherit from CUnknownStub
for having this sample making sense.

Gilles
Toris [MSFT]
2009-09-15 14:29:47 UTC
Permalink
Hi Gilles

The discussion is also mixed. Gilles, could you please write a summary of
the thread's status? Then we can continue the discussion.

Thanks
Toris
Post by Gingko
Post by Gingko
This is completely against the underlying idea of my "CUnknownStub" class
[..]
... and sorry, as we are more or less mixing samples in this thread, I
forgot to mention that both V and W classes should inherit from
CUnknownStub for having this sample making sense.
Gilles
Gingko
2009-09-17 06:09:15 UTC
Permalink
Post by Toris [MSFT]
Hi Gilles
The discussion is also mixed. Gilles, could you please write a summary of
the thread's status? Then we can continue the discussion.
Thanks
Toris
Hi Toris,

What do you mean ?

Are you asking me to start one or two new topics for continuing the
CUnknownStub part of the discussion and for the aggregate part of it ?

Anyway the discussion seems to have stopped for two weeks and that nobody
has nothing more to say about it, I'm wondering if trying to continue can
make sense.

Gilles

Loading...