As you know, in C ++ you can create an object as a global or local variable. In this case, the compiler itself cares about the destruction of the object (when leaving the block or at the end of the program), and it is impossible to call delete against it categorically (otherwise memory may be freed which cannot be freed). And you can create an object using new (we do not consider placement new). In this case, the entire responsibility for freeing the memory falls on the programmer, and at some point he is obliged to make delete with respect to the object, otherwise there will be a memory leak.

Suppose we create an object that knows when it becomes unnecessary and we would like it to delete this in this case. The question is: can an object somehow determine for itself whether it is necessary to make delete or is it selected not from a heap and therefore nothing can be done? Of course, you can pass a flag to the constructor, which will be stored in the object and used during deletion to decide what to do. However, in this case, the programmer becomes responsible for passing the correct value of the parameter depending on how the variable is described, and this is inconvenient. Are there any better solutions?

  • using std :: unique_ptr or another smart pointer (or writing your own if the requirements are very specific), actually using C ++ 11 and higher to use bare C-style pointers is generally not recommended. - strangeqargo
  • @strangeqargo to the account does not apply at all too much :) Here, if about ownership, then yes. By the way, there was a question on this topic. - αλεχολυτ

3 answers 3

If it is necessary to prohibit the creation of a class object on the stack, then it’s enough to make the constructor private, and to create objects through a special function

class Foo { public: ~Foo(); static Foo* createFoo() { return new Foo(); } private: Foo(); Foo(const Foo&); Foo& operator=(const Foo&); }; 

The code is borrowed from the enSO response.

    So to speak, freeing everyone from responsibility for my words ... :)

    Perhaps, that without overhead costs - in any way. In any particular compiler, it is possible, and it is possible to determine which memory segment the address is in, but it will be those overhead costs ...

    You can pick the memory manager - again, for a particular compiler, maybe all the dynamic memory has addresses "no less than".

    All this is at least intolerable.

    You can play around - I used to look for a leak once - redefine new , so that it adds selected addresses to the list. Then the ability to delete is defined as the presence of an address in this list.

    "I think so" (c) Pooh

    Exceptionally in the order of delirium exclusively for single-threaded ... Well, with a lot of restrictions like the order of initialization of global variables :)

     class Test { public: Test():deletable(lastIsDeletable) { lastIsDeletable = false; cout << __func__ << endl; } ~Test() { cout << __func__ << endl; } static bool lastIsDeletable; void who() const { cout << "I'm " << (deletable ? "" : "non ") << "deletable\n"; } void * operator new(size_t n) { cout << __func__ << endl; lastIsDeletable = true; return ::new unsigned char[n]; } private: bool deletable; }; bool Test::lastIsDeletable = false; Test t1; int main(int argc, const char * argv[]) { Test * t = new Test; t1.who(); t->who(); } 
    • You can use thread local variables to store the flag (for example, pthread_setspecific / pthread_getspecific). I think we can have a guarantee that the memory allocation and the constructor call will be performed by the same thread and nothing is done between them within this thread (for both the memory allocation and the constructor call are performed by the new operator, and the programmer does not even recognize the address of the object until the constructor works). As a result, we get the normal support for multithreading. I'm right? - kiv_apple
    • Again, as far as I understand, global variables of the bool type will be immediately written to the data section of the executable file, which means they will be initialized before calling any constructor. Constructors' calls will obviously be single-threaded, the standard just does not guarantee the order of calling constructors from different translation units. - kiv_apple
    • Not so well-versed in multithreading to answer yes or no confidently ... - Harry
    • By the way, what if deletable = true is done inside the overridden new operator (of course, first we need to pointer to our class)? Or is it a UB? - kiv_apple
    • @kiv_apple I wrote - in order of delirium ... :) It is. a piece for which I am not going to answer in any way :) - Harry

    Rule 27 in Scott Myers’s book More Effective C is dedicated to this subject. There he examines various ways to solve this problem. I recommend reading, if not the whole book, then at least this chapter. The author proposes to create such a class there.

     class HeapTracked{ typedef void* RawAddress; static std::list<RawAddress> addresses; public: class MissingAdress{}; virtual ~HeapTracked() = 0; static void* operator new(std::size_t size){ void *memPtr = ::operator new(size); addresses.push_front(memPtr); return memPtr; } static void operator delete(void *ptr){ std::list<RawAddress>::iterator it = std::find(addresses.begin(), addresses.end(), ptr); if(it != addresses.end()){ addresses.erase(it); ::operator delete(ptr); } else{ throw MissingAdress(); } } bool isOnHeap() const{ const void *rawAddress = dynamic_cast<const void*>(this); std::list<RawAddress>::iterator it = std::find(addresses.begin(), addresses.end(), rawAddress); return it != addresses.end(); } }; HeapTracked::~HeapTracked(){} std::list<HeapTracked::RawAddress> HeapTracked::addresses; 

    And inherited from it by those classes that need to know in the heap or not. In the above code, everything is more or less clear, except for this

     const void *rawAddress = dynamic_cast<const void*>(this); 

    It uses one interesting property dynamic_cast . If we apply dynamic_cast to a pointer to void , then we get a pointer to the beginning of the memory of the object to which it refers. Thus, casting this to const void* gives a pointer to the beginning of the current object.
    Example

    • Oh, what I wrote - "You can play around - I once looked for a leak - redefine new so that it lists selected addresses in the list. Then deletion is defined as the presence of an address in this list." . Like Jourdain, who suddenly found out what he was saying in prose :) - Harry