4

When I'm learning using abstract interface to encapsulate code in dynamic library, it seems that using "Release()" function to release resource is recommended. Now I'm wondering why they don't just use destructor to release? Is there any problem using destructor to release resource, or they just mean to use smart pointer?

This is the code recommended:

// Interface like that ..
struct IXyz
{
    virtual int Foo(int n) = 0;
    virtual void Release() = 0;
};

// Xyz class definition, derived from IXyz
// ...


// Using in client ..
IXyz* pXyz = GetXyz();  //Use Factory function to create an object

if(pXyz)
{
    pXyz->Foo(42);

    pXyz->Release();
    pXyz = nullptr;
}

now I want to write the following code instead:

// Interface
struct IXyz
{
    virtual int Foo(int n) = 0;
    virtual ~IXyz() {};    // I have moved Release()'s content into Xyz's destructor
};


// Client
IXyz* pXyz = GetXyz();  //Use Factory function to create an object

if(pXyz)
{
    pXyz->Foo(42);

    pXyz->~IXyz();
    pXyz = nullptr;
}

These code can both work correctly. So I'm wondering the differences between those two ways to release resources. Can I use both of them? Thanks a lot!

H3d9
  • 89
  • 5
  • 1
    i think this link will help you (https://stackoverflow.com/questions/16908650/what-is-the-difference-between-delete-and-calling-destructor-in-c ) – moath naji Dec 18 '19 at 04:41
  • 1
    release is not same as delete though. release is factory function for proxy object. delete returns memory to memory pool. – Mahesh Attarde Dec 18 '19 at 04:45
  • 1
    Your second code block leaks the memory of the `IXyz` object. You need to properly deallocate it. How to do that depends on what `GetXyz` does. You need to modify it as well if you skip `Release`. What does `GetXyz` do in your examples? – walnut Dec 18 '19 at 04:46
  • 1
    This is where the trouble starts. Will it leak memory or not? Will the program bomb when it uses delete? The client code has no way to know what the factory function did. Returning a pointer to a global variable is entirely valid, so is the new operator. When you use Release() then that doesn't matter, the implementation always knows how to do it correctly. – Hans Passant Dec 18 '19 at 05:55
  • @H3d9 As mentioned in the comments above, the problem is that you can't be sure *how* to delete the object, since you don't know how it was allocated. To avoid this issue `GetXyz` should return a smart pointer which knows how to handle the deallocation for you, if you don't want a manual `Release` call. – walnut Dec 18 '19 at 09:35

2 Answers2

3

This is only ever an issue on Windows. When you allocate memory via new, Win32 can use an allocation heap local to your DLL or EXE. This can cause a problem where you allocate in a release build DLL, but later free in a debug build EXE. The debug heap will then throw an error saying the memory allocation is unknown.

So to get around this, the general approach is to make sure that you allocate memory for a DLL object in the DLLs heap, and free in the same heap.

First, lets try the factory method you are describing above. Provide a DLL exported factory method to allocate the memory, e.g.

struct IXyz
{
  EXPORT static IXyz* create();
};

// in cpp file
EXPORT IXyz* IXyz::create()
{
  return new IXyz;
}

This however now means you will have to free the memory in the same place, which you can do in one of two ways. Option number one, provide a DLL exported release method.

struct IXyz
{
  EXPORT static IXyz* create();
  EXPORT void release();
};

// in cpp file
EXPORT void IXyz::release()
{
  delete this;
}

The other approach is to make the dtor virtual. It will ensure allocations are freed in the same place, but will bloat your struct size by a vtable pointer.

If you can't be bothered doing that, another approach is to export a pair of custom malloc/free funcs from a base DLL (that simply call global new/delete). Then overload new/delete in each class you export from the DSO. Using new/delete will no longer cause you issues.

Unless you are actually using DLL's heavily, AND you need to mix debug/release builds, all of the above is entirely pointless. These are patterns that have their place, but they are not good patterns to follow in general.

robthebloke
  • 9,331
  • 9
  • 12
  • Thanks, you're right. as far as I know, the best way to do this is release resource in destructor, and delete in release function. This will automatically call destructor and free memory. – H3d9 Dec 18 '19 at 09:38
2

First of all, calling the destructor destructs the object, but doesn't free it's memory. In fact, calling the destructor explecitly is very rarely needed, as delete (for dynamicly allocated objects) or going out of scope (for objects allocated on the stack/in global scope) already calls the destructor and also take care of the memory.

Now, Release() actually does something very different: It is (at least in all libraries I know of) part of something called reference counting. This is a way to control when an object should be delete-ed, depending on how many things are using it. So while you calling the destructor (either explicitly or by calling delete) will destroy the object for sure, calling Release() just tells the library that you are not using the object anymore, and the library is free to delete it whenever it feels like it - which might not be now, because the library might still use the object internally.

Bizzarrus
  • 1,194
  • 6
  • 10