2

I have a class who's constructor takes the address of an object as a parameter.

MyClass(OtherClass * otherClass);

In the Destructor of this class I try to delete the instance of OtherClass.

~MyClass() {
    if(otherClass != nullptr) {
        delete otherClass;
    }
}

The issue I'm having is when I call this constructor I call it with an element from the stack instead of from the heap, thus I call it as below:

MyClass myClass(&otherObject);

so when the myClass object goes out of scope I get an exception. How can I fond out if my OtherObject variable was declared on the stack or the heap? Or in other words, how can I know if I can delete the object?

Archmede
  • 1,592
  • 2
  • 20
  • 37
  • 3
    If you don't have any object ownership rules your code will be chaos and you're going to have problems like this. Typically passing in a pointer is "giving ownership" to that object. If you don't want that, you'll need to change that to a reference. – tadman Oct 06 '17 at 02:55
  • 2
    You should make it a clear part of your class contract whether or not it "takes ownership" of the supplied argument, and then have the code that creates the object responsible for specifying it correctly – M.M Oct 06 '17 at 02:56
  • 1
    Consider using `shared_ptr` , although this means you can never have a "stack object" given to it. Which is a good thing in terms of safety – M.M Oct 06 '17 at 02:58
  • @M.M: of course you can have a `shared_ptr` pointing to a stack (or member) object: just construct a `shared_ptr` with a suitable deleter function! I'm not using this approach frequently enough to state with confidence how that would look like but I think `shared_ptr(pointer, [](auto){})` would do. – Dietmar Kühl Oct 06 '17 at 03:35
  • 1
    @Asesh: while the question is a duplicate, I think all of the answers are pretty terrible! Of course, having provide an answer to this question I'm biased :-) – Dietmar Kühl Oct 06 '17 at 03:39
  • @DietmarKühl agree with you, voted your answer cause as it's useful :) – Asesh Oct 06 '17 at 03:48

1 Answers1

6

While there are system specific approaches which will probably be able to tell whether memory is from heap or from a stack, that actually doesn't really help: you may have got a pointer to a member of another object on the heap. The memory would be on the heap but you are still not responsible for deleting the object. Put differently: do not go down the path you are on!

The proper way to deal with the problem is to make the ownership semantics blatantly clear in the interface and go with that. There are essentially two directions you can take:

  1. Your class can deliberately not take over responsibility for the pointer passed in the constructor! Instead, it is the responsibility of the user of your class to deal with these objects and to guarantee that they are valid while your objects exists. If you can get pointers to stack (or member) objects the user already has to guarantee the validity of these objects anyway and for other ways it may be entirely trivial for the user to deal with them, e.g., by managing them via a std::unique_ptr<OtherClass>.
  2. Your class takes responsibility of all objects passed and it will delete all of them. It becomes the responsibility of the caller to not pass pointers to objects managed elsewhere, e.g., to objects on the stack or member objects.

There is sort of a hybrid approach where your class takes responsibility of objects some times but not always. However, the implementations of such an approach is really a combination of the two approaches above: you'd take a suitable smart pointer as constructor argument and it is the user's responsibility to make sure that the smart pointer is constructed appropriately by the user of your class. For example, your class could take a std::shared_ptr<OtherClass> for which the normal construction will delete the object. When the user wants to pass in a pointer to an otherwise owned object the std::shared_ptr<OtherClass> would be constructed with a deleter which does not delete the pointer. Here is a simple program demonstrating the two different management strategies for std::shared_ptr:

#include <iostream>
#include <memory>

struct foo {
    char const* name;
    foo(char const* name)
        : name(name) {
        std::cout << "foo::foo(" << name << "): " << this << "\n";
    }
    ~foo() {
        std::cout << "foo::~foo(" << name << "): " << this << "\n";
    }
};

int main() {
    std::shared_ptr<foo>(new foo("heap"));
    foo f("stack");
    std::shared_ptr<foo>(&f, [](auto){});
}
Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • By system specific approaches you are probably referring to finding the value stack pointer (esp or rsp) and the bottom of stack (esp + offset). Then you compare the value and check if it falls with in the stack's range. But remember, your code is not going to be portable, if you do so. – Asesh Oct 06 '17 at 03:45
  • @Asesh: yes, indeed: while I'm aware that on most systems you can [currently(*)] tell whether a pointer refers to the heap or the stack, I obviously recommend *not* to take advantage of this approach. (*) If/when [stackful] coroutines become available it would become harder to tell whether a pointer points to the heap or a stack as the coroutine stacks would come from the heap. However, that is just another reason for *not* taking advantage of location of an objects [for deciding whether it to be `delete`d or not]. – Dietmar Kühl Oct 06 '17 at 03:50