Re: Virtual Destructor - Implication & Specification
On Apr 9, 10:55 am, brang...@ntlworld.com (Dave Harris) wrote:
jaibudu...@gmail.com (Le Chaud Lapin) wrote (abridged):
[snipped]>
If we have code like:
// One.hpp
struct One {
void *operator new( size_t );
void operator delete( void *, size_t );
One *make();
};
Quick Note: 'make' needs to be made static...
//////////////////////////////////
// Two.cpp
#include "One.hpp"
int main() {
One *p = One::make();
delete p; // Which delete does this invoke?
}
The correct one, beacause, as the code is defined, the heaps are
obviously matched.
However, it is conceivable that one could write a function that simply
calls new() for a One object, then returns that pointer from a DLL to
the EXE. In that case, even though operator new/delete have been
defined for the class, there will still be a heap mismatch.
1. The DLL will allocate space on its heap, invoke the contstructor,
and return a pointer to the EXE.
2. The EXE will invoke the destructor against the space of the object.
If the exact same library that contains the definition of the class
member functions is used to synthesize both the EXE and the DLL, then
their will be no problem. However, if the library is is slightly
different for each (yes, very bad idea), then there will be an
exception. In any case, whether the library is exactly the same or
not for the EXE and the DLL, there will be a heap-mismatch exception
because the heap from which the object is allocated in the DLL will
not be the same as the one to which the object is deallocated in the
EXE.
Note that, in this sequence, class-specific new/delete has been
defined for the class.
However, one could argue that this method of engineering is
inappropriate, that one should be cognizant of various scenarious and
avoid putting the class definition into a static lib, and and put it
only inside a DLL. But that would be somewhat restricitve, and
additionally, every function that could possibly yield a pointer to
such object would have to be exported from the DLL.
//////////////////////////////////
// One.cpp
#include "One.hpp"
#include <cstdlib>
void *One::operator new( size_t sz ) {
return malloc( sz );
}
void One::operator delete( void *p, size_t ) {
//std::cout << "Here\n";
free( p );
}
One *One::make() {
return new One;
}
It seems to me that the language guarantees that the delete expression in
Two.cpp will invoke the class-specific delete operator in One.cpp. Are you
saying that does not happen with VC++? If so, are you also saying that
this is standard-conformant, or is it an implementation-specific quirk of
how Microsoft implements DLLs?
Because make() is a (static) member, this would not crash, and would
work. But again, if the EXE and DLL are built against a static lib
that contains the class definition, and a plain ole..
One *p = new One;
....is done inside the DLL, and p is given to the EXE to delete the
object, then there will be a crash.
[snippped]
If we had declared the class-specific new and delete inline, then they
would end up in different compilation units and effectively find different
versions of malloc() and free() in scope. We'd have a kind of violation of
the One Definition Rule. Then I would not be surprised if it went wrong.
We need the definitions to be out of line.
(Add to the code whatever extra declarations Microsoft needs to export and
import the functions. I don't have access to VC++ at the moment to test
this. Also, I wanted to show the code in pure form to be clear about the
expected behaviour if DLLs are not involved.)
I will put code at the end of this post for those who would like to
try on Windows.
This is actually what my colleagues keey saying, which I keep refuting.
I sympathise with them. Are you sure your refutations didn't define the
class-specific new and delete inline?
I think so.
This just goes to show that, if you hear incorrect information often
enough, you start repeating it.
It's not a matter of repeating what I've heard, it's a matter of what
makes sense given the C++ language definition. At this point I would like
to know whether I've misunderstood the standard (in which case please give
details), or whether this is just some idiosyncrasy of VC++ (I won't call
it a bug because it involves DLLs, but I want to), or what.
I guess its that, under not-so-extraordinary circumstances (EXE-links-
with-LIB, DLL-links-with-LIB), you can still have class-specific
operator new/delete inside the LIB, and there will be a crash.
Finally, elsewhere you have talked about a Virtually_Destructible template
which works around this problem. Everyone who reads this thread ought also
be aware of boost::shared_ptr, which also provides a solution.
Even if the object that is being pointed to does not have a virtual
destructor? Not familiar with boost::shared_ptr. Just curious.
-Le Chaud Lapin-
// EXE.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "..\Foo.hpp"
#include <intrin.h>
int _tmain(int argc, _TCHAR* argv[])
{
Abstract *pAbstract = gimme_abstract();
Concrete *pConcrete = gimme_concrete();
Concrete *p = Concrete::make();
delete p; // Which delete does this invoke?
delete pAbstract;
delete pConcrete;
return 0;
}
// DLL.cpp : Defines the entry point for the DLL application.
//
#include <windows.h>
#define DEFINE_EXPORTS
#include "..\Foo.hpp"
Abstract * gimme_abstract ()
{
return new Abstract;
}
Concrete * gimme_concrete ()
{
return new Concrete;
}
#include <cstdlib>
void *Concrete::operator new( size_t sz ) {
return malloc( sz );
}
void Concrete::operator delete( void *p, size_t )
{
//std::cout << "Here\n";
free( p );
}
Concrete *Concrete::make() {
return new Concrete;
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
return TRUE;
}
// FOO.HPP
struct Abstract
{
virtual ~Abstract ();
} ;
struct Concrete
{
#ifdef DEFINE_EXPORTS
_declspec (dllexport) void *operator new( size_t );
_declspec (dllexport) void operator delete( void *, size_t );
_declspec (dllexport) static Concrete *make();
#else
_declspec (dllimport) Abstract * gimme_abstract ();
_declspec (dllimport) Concrete * gimme_concrete ();
_declspec (dllimport) void *operator new( size_t );
_declspec (dllimport) void operator delete( void *, size_t );
static Concrete *make();
#endif
~Concrete (){}
} ;
#ifdef DEFINE_EXPORTS
_declspec (dllexport) Abstract * gimme_abstract ();
_declspec (dllexport) Concrete * gimme_concrete ();
#else
_declspec (dllimport) Abstract * gimme_abstract ();
_declspec (dllimport) Concrete * gimme_concrete ();
#endif
// FOO.CPP
#include "stdafx.h"
#include "..\Foo.hpp"
#include <Windows.h>
Abstract::~Abstract ()
{
Beep(500, 300);
}
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]