It certainly has its use cases, e.g. classes with intrusive reference count, where the "release" method would destroy the object once the refcount has reached 0.
struct foo {
atomic<int> refcount;
// other stuff
};
You can pass around a pointer, COM-style, and call ->Release as needed. But you can be a lot less error-prone by passing around a smart pointer that understands the intrusive refcount and handles releasing. At which point you get something like 'delete ptr' in a destructor, not 'delete this' in a Release function.
> 'delete ptr' in a destructor, not 'delete this' in a Release function
That approach requires client and server to be written in the same language, use the same memory allocator, and same compiler. For COM objects, often all of these are false.
For example, C#, PowerShell or VBScript code can call IUnknown.Release() method of C++ implemented COM objects, which will cause C++ code to deallocate the memory. However, these higher level languages can’t directly delete C++ objects: they know nothing about C++ runtime, or C runtime.
In practice, people would (hopefully) wrap the whole thing in a smart pointer anyway, instead of manually calling addRef() and release(). But for COM style interfaces you need the release() method because you only want to expose pure virtual interfaces.