0

Have a look at following code:

#include <iostream>
#include <memory>

class A
{
public:
    A()
    {
        std::cout << "A() \n";
    }

    ~A()
    {
        std::cout << "~A() \n";
    }

    int a = 100;
};

class B
{
public:
    B()
    {
        ptr.reset(new A());
        std::cout << "B() \n";
        std::cout << "pointer value" << ptr.get() << std::endl;
        std::cout << ptr->a << std::endl;
    }

    ~B()
    {
        std::cout << "~B() \n";
    }

    void print()
    {
        std::cout << "print() " << a << b << "\n";
        std::cout << "pointer value" << ptr.get() << std::endl;
        std::cout << ptr->a << std::endl;
    }
private:
    std::unique_ptr<A> ptr;
    int a = 10;
    int b = 5;
};

int main()
{
    std::unique_ptr<B> p1(new B());   
    p1->~B();
    p1->print();
    return 0;
}

the output result:

A()
B()
pointer value010ECB60
100
~B()
~A()
print() 105
pointer value010ECB60
-572662307
~B()
~A()

The Question is:

  1. When class B's destructor called, class A's destructor also called, and class A's member a = 100 has been destroyed, but class B's member a = 10; b = 5 still exist, and it's value not changed, so class B's destructor can't be seen as a function, since it called class A's destructor, how to explain this?
  2. When exit the main() function, the destructor of class B called second time automatically, and code breaks with the error code HEAP[Destructor.exe]: Invalid address specified to RtlValidateHeap( 00EA0000, 00EACE38 ), because class B's destructor called first time so object A has been destroyed, this will destroy it twice?

I add some print info to track workflow, but i still don't figure out how it worked when called the destructor explicitly.

Grey
  • 5
  • 3
  • 2
    `p1->~B();` -- Don't do this unless you are using `placement-new`. You can experiment all you want to, but doing this outside of using it for `placement-new` is nothing more than a fool's errand. – PaulMcKenzie Dec 30 '22 at 08:02
  • *"Is object can be destroyed when called the destructor explicitly?"* Yes it can. – Jason Dec 30 '22 at 08:06
  • 1
    https://stackoverflow.com/questions/6441218/can-a-local-variables-memory-be-accessed-outside-its-scope Different situation, but accepted answer still stands. – Revolver_Ocelot Dec 30 '22 at 08:07
  • You can't determine that something exists by looking at its parts; what's left of a destroyed object doesn't physically disappear. ("He can't be dead since his wallet was still in his pocket and all his money was in it" will never hold up in court.) – molbdnilo Dec 30 '22 at 08:08
  • 1
    *I add some print info to track workflow* -- You either know what you did was "bad" or you don't. Using print statements to figure this out is not how to go about verifying this. When you invoke undefined behavior, print statements are worthless. – PaulMcKenzie Dec 30 '22 at 08:11

3 Answers3

2

When class B's destructor called, class A's destructor also called, and class A's member a = 100 has been destroyed, but class B's member a = 10; b = 5 still exist, and it's value not changed, so class B's destructor can't be seen as a function, since it called class A's destructor, how to explain this?

No, they do not exist, both B and A objects have been destroyed, it is just that dereferencing a dangling pointer (p1.get() during p1->print()) is undefined behaviour. In this case, the compiler just did not bother with clearing the memory locations used for the storage of the object B.

When exit the main() function, the destructor of class B called second time automatically, and code breaks with the error code HEAP[Destructor.exe]: Invalid address specified to RtlValidateHeap( 00EA0000, 00EACE38 ), because class B's destructor called first time so object A has been destroyed, this will destroy it twice?

Well, you destroyed the object held by p1 but in a way p1 was not aware it has been destroyed. Then when ~unique_ptr has been called on p1, it tried to destroy already destroyed object. Instead, you should just use p1.reset() which will call ~B and update p1's state accordingly to prevent the mentioned issue.

Quimby
  • 17,735
  • 4
  • 35
  • 55
  • Note that calling `p1.reset()` will call the destructor and cause the same double destruction. You can call `p1.release()` in this case (on a object that has been explicitly destroyed but not yet deallocated), which will result in a memory leak. There's no safe way of deallocating the object in this case -- another reason why one should never call the the destructor explicitly – Chris Dodd Dec 31 '22 at 01:32
  • @ChrisDodd Perhaps I did not phrase it correctly, I meant to say "call `p1.reset()` instead of `p1->~B()`". Otherwise you are right. – Quimby Dec 31 '22 at 07:00
1

To really understand what is going on here, you need to understand there are really two related processes that happen when recycling an object's memory. There's destruction (which is calling the class destructor to clean up any other objects the object refers to), and there's deallocation (which makes the memory available for reuse). Normally for objects on the heap, one calls delete which does both of these things. For std::unique_ptr, its destructor call its deleter, which by default calls delete on the object the unique_ptr points at.

When you call a destructor explicitly (with p1->~B() in your case), it destroys the object but does not deallocate it. That leaves the object in a "broken" state where you can't do anything with it (not even call delete safely), but where it hasn't been deallocated either, so all you can do is kill any references to it (with .release()) and let the memory leak. So you should never call the destructor explicitly.

If you try to access an object after it has been destroyed, you get undefined behavior, but if you try it before the object has been deallocated (as you are doing in your example), you'll probably just see the same data, as the memory has not yet been overwritten by anything else.

Chris Dodd
  • 119,907
  • 13
  • 134
  • 226
0

Usually you don't need to call destructor explicitly. For automatic objects and dynamically allocated objects (using new/delete) lifetime of object coincides with liftime of its storage, and constructors and destructors called automatically. But sometimes storage is allocated separatelly and in this case constructor(well, sort of) and destructor should be called explicitly. Here is example code from: https://en.cppreference.com/w/cpp/language/new

{
    // Statically allocate the storage with automatic storage duration
    // which is large enough for any object of type `T`.
    alignas(T) unsigned char buf[sizeof(T)];
 
    T* tptr = new(buf) T; // Construct a `T` object, placing it directly into your 
                          // pre-allocated storage at memory address `buf`.
 
    tptr->~T();           // You must **manually** call the object's destructor
                          // if its side effects is depended by the program.
}                         // Leaving this block scope automatically deallocates `buf`.

Same techniques used in different containers, for example std::vector.

sklott
  • 2,634
  • 6
  • 17