Using shared_ptr in dll-interfaces

前端 未结 4 1429
闹比i
闹比i 2020-12-02 19:12

I have an abstract class in my dll.

class IBase {
  protected:
       virtual ~IBase() = 0;
  public:
       virtual void f() = 0;
};

I wan

相关标签:
4条回答
  • 2020-12-02 19:29

    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:

    1. 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.

    2. 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.

    0 讨论(0)
  • 2020-12-02 19:42
    1. Using shared_ptr will make sure the resource releasing function will be called in the DLL.
    2. Have a look at the answers to this question.

    A way out of this problem is to create a pure C interface and a thin fully inlined C++ wrapper around it.

    0 讨论(0)
  • 2020-12-02 19:50

    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.

    0 讨论(0)
  • 2020-12-02 19:52

    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.

    0 讨论(0)
提交回复
热议问题