I have an abstract class in my dll.
class IBase {
protected:
virtual ~IBase() = 0;
public:
virtual void f() = 0;
};
I wan
I would advise against using shared_ptr
in the interface. Even using C++ at all in the interface of a DLL (as opposed to "extern C" only routines) is problematic because name-mangling will prevent you from using the DLL with a different compiler. Using shared_ptr
is particularly problematic because, as you have already identified, there is no guarantee that the client of the DLL will use the same implementation of shared_ptr
as the caller. (This is because shared_ptr
is a template class and the implementation is contained entirely in the header file.)
To answer your specific questions:
I'm not quite sure what you are asking here... I'm assuming that your DLL will contain implementations of classes derived from IBase
. The code for their destructors (as well as the rest of the code) will, in both of your cases, be contained in the DLL. However, if the client initiates the destruction of the object (by calling delete
in the first case or by letting the last instance of the shared_ptr
go out of scope in the second case), then the destructor will be called from client code.
Name-mangling will usually prevent your DLL from being used with a different compiler anyway... but the implementation of shared_ptr
may change even in a new release of the same compiler, and that can get you into trouble. I would shy away from using the second option.
shared_ptr
will make sure the resource releasing function will be called in the DLL. A way out of this problem is to create a pure C interface and a thin fully inlined C++ wrapper around it.
An answer to your first question: The virtual destructor in your dll is called - the information about its location is embedded in your object (in the vtable). In the case of memory deallocation it depends how disciplined the users of your IBase
are. If they know they have to call Release()
and consider that exception can bypass the control flow in an surprising direction, the right one will be used.
But if CreateInterface()
returns shared_ptr<IBase>
it can bind the right deallocation function right to this smart pointer. Your library may look like this:
Destroy(IBase* p) {
... // whatever is needed to delete your object in the right way
}
boost::shared_ptr<IBase> CreateInterface() {
IBase *p = new MyConcreteBase(...);
...
return shared_ptr<IBase>(p, Destroy); // bind Destroy() to the shared_ptr
} // which is called instead of a plain
// delete
Thus every user of your DLL is easily prevented against resource leaks. They never have to bother about calling Release()
or pay attention to exceptions bypassing surprisingly their control flow.
To answer your second question: The downside of this approach is clearly stated by the other answers: You're audience has to use the same compiler, linker, settings, libraries as you. And if they could be quite a lot this can be major drawback for your library. You have to choose: Safety vs. larger audience
But there's a possible loophole: Use shared_ptr<IBase>
in your application, i.e.
{
shared_ptr<IBase> p(CreateInterface(), DestroyFromLibrary);
...
func();
...
}
Thus no implementation specific object is passed across the DLL boundary. Nevertheless your pointer is safely hidden behind the shared_ptr
, who's calling DestroyFromLibrary
at the right time even if func()
's throwing an exception or not.
On your first question: I'm taking an educated guess and not speaking from experience, but it seems to me that the second case the memory deallocation will be called "in the .exe". There are two things that happen when you call delete object;
: first, destructors are called and second, the memory for the object is freed. The first part, destructor calling, will definitely work as you expect, calling the right destructors in your dll. However, since shared_ptr is class template, its destructor is generated in your .exe, and therefore it will call operator delete() in your exe and not the one in the .dll. If the two were linked against different runtime versions (or even statically linked against the same runtime version) this should lead to the dreaded undefined behavior (this is the part I'm not entirely sure about, but it seems logical to be that way). There's a simple way to verify if what I said is true - override the global operator delete in your exe, but not your dll, put a breakpoint in it and see what's called in the second case (I'd do that myself, but I have this much time for slacking off, unfortunately).
Note that the same gotcha exist for the first case (you seem to realize that, but just in case). If you do this in the exe:
IBase *p = CreateInterface();
delete p;
then you are in the same trap - calling operator new in the dll and calling operator delete in the exe. You'll either need a corresponding DeleteInterface(IBase *p) function in your dll or a Release() method in IBase (which doesn't have to be virtual, just not make it inline) for the sole purpose of calling the right memory deallocation function.