Search code examples
c++memory-managementlanguage-lawyerlifetimeexplicit-destructor-call

is it required to call a non-trivial destructor when it is a noop?


is it required by the standard to call non-trivial destructor when you know that in this specific case the destructor is a noop ?

is the code likely to be broken by compliers if the destructor is not called ?

the use case is a class that contain a dynamicly allocated pointer. by default this pointer is obtained by new in the constructor. this class can also get its dynamicly allocated pointer from an allocator. the class keeps track of how it obtained its pointer and calls delete in the destrucor if the pointer was obtained by new and nothing if it was obtained by the allocator because the allocator will free the memory. the data stored in the dynamic memory is only trivial type so their destructor doesn't need to be called.

so the question is do i still need to call the destructor on the class if i know that it obtained its pointer via the allocator so the destructor is a noop ?

this is a minimal simplified example everything not directly related to the issue was removed.

struct Allocator {
    void* ptr = nullptr;
    void* Allocate(size_t size) {
        ptr = malloc(size);
        return ptr;
    }
    ~Allocator() { // allocator will cleanup
        if (ptr)
            free(ptr);
    }
};

struct C {
    int* ptr;
    bool need_cleanup;
    C() {
        ptr = new int[10];
        need_cleanup = true;
    }
    C(Allocator& A) {
        ptr = (int*)A.Allocate(10 * sizeof(int));
        need_cleanup = false;
    }
    ~C() { // non-triviall because user-defined.
        if (need_cleanup)
            delete[] ptr;
        // noop if need_cleanup is false.
    }
};

int main()
{
    Allocator A;
    alignas(C) char buffer[sizeof(C)];
    C* c = new(buffer) C(A);
    /// is it required to call c->~C();
}

Solution

  • No.

    For an object of a class type with a non-trivial destructor, the program is not required to call the destructor explicitly before the storage which the object occupies is reused or released; however, if there is no explicit call to the destructor or if a delete-expression ([expr.delete]) is not used to release the storage, the destructor shall not be implicitly called and any program that depends on the side effects produced by the destructor has undefined behavior.

    [basic.life]

    You are not depending on any side-effects of ~C, so you don't have undefined behavior.

    N.B. you should probably placement new[] your A.Allocate'd int[10]

    C(Allocator& A) {
        ptr = new (A.Allocate(10 * sizeof(int))) int[10];
        need_cleanup = false;
    }