Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the point of deleted virtual functions?

Apparently you can =delete virtual functions:

struct A
{
    virtual void foo() = delete;
};

struct B : A
{
    void foo() override = delete;
};

Interestingly, both functions have to be =deleted, or both not. You get a compilation error otherwise.

What is the point of this feature? It seems some thought went into this (since inconsistent deletion is banned, see above), so it looks like this was allowed intentionally?

like image 978
HolyBlackCat Avatar asked Sep 03 '25 04:09

HolyBlackCat


2 Answers

It's defined like this because of overload resolution, but mainly to be consistent with non-deleted virtuals, and = deleted non-virtuals.

If you have overloads of foo in the base class, and want some (but not all1) declared in the derived class, then you will need to have the deleted override declaration to keep the deleted one a candidate in overload resolution.

Having a deleted virtual function then prohibits any derived class from re-introducing a member function of the same signature because you can't shadow a virtual function, only override it.

struct A
{
    virtual void foo(int) = delete;
    virtual void foo(double);
    virtual void foo(const char*);
};

struct B : A
{
    void foo(int) override = delete;
    void foo(double) override;
};

struct C : A
{
    void foo(int); // error, redeclaring a deleted virtual function without delete
};

int main() {
    B b;
    A& a = b;
    a.foo(1); // error, calling deleted function
    a.foo(1.); // B::foo(double)
    a.foo("1"); // A::foo(const char*)
    b.foo(1); // error, calling deleted function
    b.foo(1.); // B::foo(double)
    b.foo("1"); // error, A::foo(const char*) is shadowed by B::foo declarations
}
  1. If you want them all, then you can have using A::foo; in B
like image 174
Caleth Avatar answered Sep 05 '25 01:09

Caleth


It appears to be an ABI compatibility tool, to add dummy entries to the vtable.

Despite being impossible to call in legal C++, those functions still get entries in the vtable that crash the program when executed, like pure virtual functions (as specified in the Itanium ABI, and can also be confirmed experimentally on MSVC ABI).

You can confirm this with the following example: (which technically violates ODR)

struct A
{
    virtual void a() {std::cout << "a\n";}

    // If you comment this out, calling `c()` in the other file crashes.
    virtual void b() = delete;

    virtual void c() {std::cout << "c\n";}
};


void foo(A &);

int main()
{
    A a;
    foo(a);
}
struct A
{
    virtual void a() = 0;
    virtual void b() = 0;
    virtual void c() = 0;
};

void foo(A &a)
{
    a.a();
    // a.b(); // Crashes.
    a.c(); // Crashes if you comment out the deleted `b()` in the other file.
}

While you can use this to disable some overloads, this wastes space in the vtable, so it doesn't feel like a good idea (despite the cost being minimal).

Especially given that making deleted functions non-virtual works equally well, except for one exception shown in @Caleth's answer, which is that derived classes can shadow deleted non-virtual functions with non-deleted functions, but can't do this with deleted virtual functions.

like image 23
HolyBlackCat Avatar answered Sep 05 '25 00:09

HolyBlackCat