19

Preface:

This is a question about best practices regarding a new meaning of the delete operator introduced with C++11 when applied to a child class overriding an inherited parent's virtual method.

Background:

Per the standard, the first use case cited is to explicitly disallow calling functions for certain types where conversions would otherwise be implicit such as the example from §8.4.3 of the latest C++11 standard draft:

struct sometype {
    sometype() = delete; // OK, but redundant
    some_type(std::intmax_t) = delete;
    some_type(double);
};

The above example is clear and purposeful. However, the following example where the new operator is overridden and prevented from being called by defining it as deleted started me thinking about other scenarios that I later identify in the question section (the example below is from §8.4.3 of the C++11 standard draft):

struct sometype {
    void *operator new(std::size_t) = delete;
    void *operator new[](std::size_t) = delete;
};
sometype *p = new sometype; // error, deleted class operator new
sometype *q = new sometype[3]; // error, deleted class operator new[]

Question:

By extension of this thought to inheritance, I am curious to other's thoughts regarding whether the following usage example is a clear and valid use-case or if it is an unclear abuse of the newly added feature. Please provide justification for your answer (the example that provides the most compelling reasoning will be accepted). In the example that follows, the design attempts to maintain two versions of library (the library is required to be instantiated) by having the second version of the library inherit from the first. The idea is to allow bug fixes or changes made to the first library version to automatically propagate to the second library version while allowing the second library version to focus only on its differences from the first version. To deprecate a function in the second library version, the delete operator is used to disallow a call to the overridden function:

class LibraryVersion1 {
public:
    virtual void doSomething1() { /* does something */ }
    // many other library methods
    virtual void doSomethingN() { /* does something else */ }
};

class LibraryVersion2 : public LibraryVersion1 {
public:
    // Deprecate the doSomething1 method by disallowing it from being called
    virtual void doSomething1() override = delete;

    // Add new method definitions
    virtual void doSomethingElse() { /* does something else */ }
};

Though I can see many benefits to such an approach, I think I tend more toward the thought that it is an abuse of the feature. The primary pitfall I see in the above example is that the classic "is-a" relationship of inheritance is broken. I have read many articles that strongly recommend against any use of inheritance to express a "sort-of-is-a" relationship and to instead use composition with wrapper functions to clearly identify the relationships of the classes. While the following often-frowned-upon example requires more effort to implement and maintain (regarding the number of lines written for this piece of code, since every inherited function to be available publicly must be explicitly called out by the inheriting class), the use of delete as depicted above is very similar in many ways:

class LibraryVersion1 {
public:
    virtual void doSomething1() { /* does something */ }
    virtual void doSomething2() { /* does something */ }
    // many other library methods
    virtual void doSomethingN() { /* does something */ }
};

class LibraryVersion2 : private LibraryVersion1 {
    // doSomething1 is inherited privately so other classes cannot call it
public:
    // Explicitly state which functions have not been deprecated
    using LibraryVersion1::doSomething2();
    //  ... using (many other library methods)
    using LibraryVersion1::doSomethingN();

    // Add new method definitions
    virtual void doSomethingElse() { /* does something else */ }
};

Thank you in advance for your answers and further insight into this potential use-case of delete.

Cœur
  • 37,241
  • 25
  • 195
  • 267
statueuphemism
  • 644
  • 2
  • 5
  • 13
  • I don't think `virtual void doSomething1() override = delete;` is legal. What would you have `((LibraryVersion1*)(new LibraryVersion2))->doSomething1()` do? – Andrew Tomazos Jan 16 '13 at 19:01
  • Per my understanding, even with the cast to LibraryVersion1, the deleted function would still attempted to be called given the override by LibraryVersion2 and cause the code to fail to compile. This is where the "is-a" relationship is broken as noted in my question, but it would certainly enforce deprecation as intended. – statueuphemism Jan 16 '13 at 19:06
  • fresh: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3485.pdf – qPCR4vir Jan 16 '13 at 19:08
  • i don't think that declaring a virtual function as deleted C++11 is legal. neither Clang nor GCC allow that – Andy Prowl Jan 16 '13 at 19:21
  • @statueuphemism: Code that is compiled against `LibraryVersion1*` doesn't know anything about `LibraryVersion2`, so how can it know statically (at compile time) that at some future date it will be deleted in some subclass. Virtual functions are dynamically dispatched. Maybe it could throw a runtime exception, but you can do that by overriding the function with `{ throw DeprecatedFunctionException }`, so it isn't worth putting in as a language feature. – Andrew Tomazos Jan 16 '13 at 19:22
  • For what it's worth, public inheritance with a private using declaration for the function signature you wish to hide appears to do exactly the same thing: https://stackoverflow.com/a/53566180/1689844 I definitely wouldn't recommend it and agree with the most up-voted answers that recommends private inheritance with specific using declarations to make things public or to use composition. – statueuphemism Apr 24 '20 at 13:22
  • Does this answer your question? [Delete virtual function from a derived class](https://stackoverflow.com/questions/24609872/delete-virtual-function-from-a-derived-class) – imz -- Ivan Zakharyaschev Sep 13 '21 at 18:46

3 Answers3

11

Paragraph 8.4.3/2 of the C++ Standard indirectly forbids deleting a function which overrides a virtual function:

"A program that refers to a deleted function implicitly or explicitly, other than to declare it, is ill-formed. [ Note: This includes calling the function implicitly or explicitly and forming a pointer or pointer-to-member to the function"

Invoking an overriding virtual function through a pointer to the base class is an attempt to implicitly invoke the function. Therefore, per 8.4.3/2, a design that allows this is illegal. Also notice that no C++11 conforming compiler will let you delete an overriding virtual function.

More explicitly, the same is mandated by Paragraph 10.3/16:

"A function with a deleted definition (8.4) shall not override a function that does not have a deleted definition. Likewise, a function that does not have a deleted definition shall not override a function with a deleted definition."

Andy Prowl
  • 124,023
  • 23
  • 387
  • 451
  • 2
    Thank you. I somehow missed that gem. Just found another quote that forbids it directly: "A function with a deleted definition (8.4) shall not override a function that does not have a deleted definition. Likewise, a function that does not have a deleted definition shall not override a function with a deleted definition." – statueuphemism Jan 16 '13 at 19:30
  • @statueuphemism: right, good point. let me integrate that in my answer – Andy Prowl Jan 16 '13 at 20:17
6

10.3p16:

A function with a deleted definition (8.4) shall not override a function that does not have a deleted definition. Likewise, a function that does not have a deleted definition shall not override a function with a deleted definition.

The other answers explain why pretty well, but there you have the official Thou Shalt Not.

aschepler
  • 70,891
  • 9
  • 107
  • 161
3

Consider some function:

void f(LibraryVersion1* p)
{
    p->doSomething1();
}

This will compile before LibraryVersion2 is even written.

So now you implement LibraryVersion2 with the deleted virtual.

f has already been compiled. It doesn't know until runtime which LibraryVersion1 subclass it has been called with.

This is why a deleted virtual isn't legal, it doesn't make any sense.

Best you can do is:

class LibraryVersion2 : public LibraryVersion1
{
public:
    virtual void doSomething1() override
    {
         throw DeletedFunctionException();
    }
}
Andrew Tomazos
  • 66,139
  • 40
  • 186
  • 319
  • 1
    I would upvote this as well if I had the reputation to do so. Regarding the "best you can do" supplement though, throwing an exception at runtime to signal deprecation of a method seems messy to me when it comes to deprecating a function (I certainly wouldn't want to use a library that did that). I think that this situation would be begging for a redesign at that point. But you're right, that's likely the best that the language could enforce for a deleted definition of an overridden method. – statueuphemism Jan 16 '13 at 19:40
  • It isn't clear what the problem you are trying to solve is. You seem to want a way to manage library versioning. So first library 1 is written, then application 1 is written using library 1, then library 2 is written and you want application 1 to keep working with library 2. – Andrew Tomazos Jan 16 '13 at 19:45
  • My example was a bit contrived and I am not actually trying to solve a problem at present. I was positing the idea as a potential method to simply maintain two pieces of software where one piece of software inherits directly from the other without any additional merging required (kind of like Python 2.7 which has some commonalities with Python 3.0+ but is still being maintained due to significant differences). If you were to maintain a Python3_0 class inheriting from Python2_7, bug fixes in shared code would automatically be propagated. There are other better designs to accomplish this though. – statueuphemism Jan 16 '13 at 19:58
  • Apparently, you defined a DeletedFunctionException not in the std library. You should show that definition. – Scott Hutchinson Feb 01 '22 at 16:48