5

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();
}
Tyker
  • 2,971
  • 9
  • 21
  • 3
    Please add a [mcve] to aid interpreting your question. – Richard Critten Jun 12 '19 at 10:05
  • I think we need a code snippet. What do you mean by "call a non-trivial destructor"? – Bathsheba Jun 12 '19 at 10:05
  • 2
    non-trivial and noop seems contradictory – Petr Skocik Jun 12 '19 at 10:08
  • I barely call destructors explicitly, if trivial, my optimizer will do its job. So I don't see much of a reason to skip it, can you share your code for some insights? – JVApen Jun 12 '19 at 10:09
  • There are very few circumstances where your code will directly call a destructor. Generally the destructor will be called when the object's life ends. If the object is created with a `new` expression, then the destructor is called by the corresponding `delete` expression. A `delete` expression does nothing on a null pointer. For a trivial type, the compiler will take care of clean-up as required. For a non-trivial type, you should not prevent the destructor being called - i.e. you must ensure the object's life ends when needed, so the destructor is called. – Peter Jun 12 '19 at 10:10
  • added an example to explain my point – Tyker Jun 12 '19 at 10:15
  • You can deallocate an array with single object `delete`! – curiousguy Jun 12 '19 at 10:15

1 Answers1

10

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;
}
Caleth
  • 52,200
  • 2
  • 44
  • 75
  • is the placement new required even for trivial type like int ? – Tyker Jun 12 '19 at 10:51
  • 1
    @Tyker I think so, because you haven't started the array's lifetime otherwise – Caleth Jun 12 '19 at 10:55
  • yes but C code does this all the time so i thought it was legal for trivial types. – Tyker Jun 12 '19 at 10:56
  • 2
    C++ is not C. I would caution against trying to trick C++ into not doing "noop" things, because you are likely to arrive at undefined behaviour. If you instead let the compiler do it's job, the generated output is likely to be the minimal thing you intend – Caleth Jun 12 '19 at 10:59
  • i agree that it is risky but it can bring improvement in performances. for example if a whole data structure is allocated using the same costum allocator and the allocator can free the memory. you can just stop using the data structure and ask the allocator to free. instead of going thought it and calling all destructors. this is my use case – Tyker Jun 12 '19 at 11:04
  • 1
    No, it won't. `new (A.Allocate(10 * sizeof(int))) int[10];` compiled by g++ will result in the same object code as `(int *)malloc(10 * sizeof(int))` compiled by gcc. – Caleth Jun 12 '19 at 11:08
  • for the placement the new case it can't bring better performances i agree and i will use it because it is free and safer. but i was talking about not calling destructors when they aren't needed. sorry i didn't make it clear. – Tyker Jun 12 '19 at 11:12
  • 3
    @Tyker • Trust the C++ optimizer to eliminate the noop destructor if it is really not needed. Don't try to hand-optimize it out, because therein lies the tightrope over the pit of UB. – Eljay Jun 12 '19 at 11:24