Why is delete operator required for virtual destructors
Asked Answered
G

2

14

In a freestanding context (no standard libraries, e.g. in operating system development) using g++ the following phenomenon occurs:

class Base {
public:
   virtual ~Base() {}
};

class Derived : public Base {
public:
    ~Derived() {}
};

int main() {
    Derived d;
}

When linking it states something like this: undefined reference to operator delete(void*)

Which clearly means that g++ is generating calls to delete operator even though there are zero dynamic memory allocations. This doesn't happen if destructor isn't virtual.

I suspect this has to do with the generated vtable for the class but I'm not entirely sure. Why does this happen?

If I must not declare a delete operator due to the lack of dynamic memory allocation routines, is there a work around?

EDIT1:

To successfully reproduce the problem in g++ 5.1 I used:

g++ -ffreestanding -nostdlib foo.cpp

Glottochronology answered 28/7, 2015 at 20:32 Comment(7)
I can’t reproduce the problem for this simple example. Are you sure you’re not missing something?Brussels
@RobinKrahl did you try adding -ffreestanding to the g++ command line. Check on the disassembly dump if there are any calls to delete operator.Glottochronology
Compiles using g++ 4.8.4 on my Linux Mint. Used g++ Testing.cpp -ffreestanding. But with clang 3.5.0 I am getting a bunch of linker errors.Saltcellar
Maybe a stupid question: What does -nostdlib do ??? (remove operator delete(void*) ? )Dogbane
@DieterLücking It skips linking standard C++ library (STL, default operators, personalities, exception handling, stack unwinding and so on)Homesick
@DieterLucking Yes. Usually c++ programs are linked against c++ library, but when developing an operative system you don't have any of those libraries, so there is not default implementation of the operator deleteGlottochronology
@Homesick sorry, I misread your commentMeroblastic
H
15

Because of deleting destructors. That are functions that are actually called when you call delete obj on an object with virtual destructors. It calls the complete object destructor (which chains base object destructors — the ones that you actually define) and then calls operator delete. This is so that in all places where delete obj is used, only one call needs to be emitted, and is also used to call operator delete with the same pointer that was returned from operator new as required by ISO C++ (although this could be done more costly via dynamic_cast as well).

It's part of the Itanium ABI that GCC uses.

I don't think you can disable this.

Homesick answered 28/7, 2015 at 20:42 Comment(9)
Thanks for the answer. Agree with @Yakk. Now I get what happens. You think is there a workaroundGlottochronology
@Felipe since the deleting destructor will only be called from a delete, and since you don't have one, you could implement your own delete(void*) and have it do nothing or generate a runtime error.Camion
@MarkRansmo You are saying that there is no chance it gets called by a normal destruction of a derived class?Glottochronology
@Felipe No, deleting destructors are called only with deleteHomesick
That's the solution then. ThanksGlottochronology
@MarkRansom out of curiosity, in OP's main function he didn't use the delete operator, so the deleting constructor shouldn't be called in this case, should the delete operator be implemented nonetheless? Or is there something I've misunderstood?Gastrostomy
@Gastrostomy the idea is to provide a dummy function to eliminate the linker error, since the function should never be called anyway. I suggested having it generate an error so that it's easy to detect if someone modifies the code and tries to use delete in the future.Camion
I think there is an additional reason you didn't mention (explicitly): It is possible to overload operator delete in a derived class; the standard [class.free]p4 requires that delete is dispatched dynamically (effectively) if the static type of the delete operand has a virtual dtor. That is, if you define a virtual destructor, you'll effectively also get a virtual operator delete. Live exampleLaforge
I had this same question (which arose when I upgraded from GCC 4.9 to GCC 5.4.1). Are we saying that there is one call that is made when either delete is called on one of these objects or it goes out of scope? In the latter case what pointer would be passed to delete?Senegambia
S
1

In C++20 there is now a fix: P0722R3. The static void operator delete(T*, std::destroying_delete_t) deallocation function. It essentially maps to the destroying destructor.

You can just make it not call ::operator delete, like:

class Base {
public:
    void operator delete(Base* p, std::destroying_delete_t) {
        // Shouldn't ever call this function
        std::terminate();  // Or whatever abort-like function you have on your platform

        // The default implemenation without any overrides basically looks like:
        // p->~Base(); ::operator delete(p);
        // Which is why the call to `operator delete` is generated
    }
    virtual ~Base() {}
};

class Derived : public Base {
public:
    // Calls Base::operator delete in deleting destructor, so no changes needed
    ~Derived() {}
};

int main() {
    Derived d;
}

The deleting destructor is the one called when you do delete ptr_to_obj;. It can only be called by delete expressions, so if you have none in your code, this should be fine. If you do, you can replace them with ::delete ptr_to_obj; and the deleting destructor will no longer be called (it's purpose is to call overriden operator delete's for classes, and ::delete will only call the global ::operator delete)

Sleazy answered 9/11, 2020 at 18:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.