18

前言:

这是一个关于 C++11 中引入的删除运算符的新含义的最佳实践问题,该运算符应用于覆盖继承父项的虚拟方法的子类。

背景:

根据标准,引用的第一个用例是明确禁止调用某些类型的函数,否则转换将是隐式的,例如最新C++11 标准草案第 8.4.3 节中的示例:

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

上面的例子是明确而有目的的。但是,下面的示例 new 运算符被覆盖并通过将其定义为已删除来防止被调用,这让我开始思考我稍后在问题部分中确定的其他场景(下面的示例来自C++的 §8.4.3 11标准草案):

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[]

问题:

通过将这种想法扩展到继承,我很好奇其他人的想法,即以下使用示例是否是一个清晰有效的用例,或者它是否是对新添加功能的不明确滥用。请为您的回答提供理由(提供最有说服力的例子将被接受)。在下面的示例中,设计尝试通过让库的第二个版本继承自第一个版本来维护两个版本的库(需要对库进行实例化)。这个想法是允许对第一个库版本的错误修复或更改自动传播到第二个库版本,同时允许第二个库版本只关注其与第一个版本的差异。要在第二个库版本中弃用函数,

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 */ }
};

虽然我可以看到这种方法有很多好处,但我认为我更倾向于认为这是对该功能的滥用。我在上面的例子中看到的主要缺陷是经典的“is-a”继承关系被打破了。我读过很多文章,强烈建议不要使用继承来表达“某种即是”的关系,而是使用带有包装函数的组合来清楚地识别类的关系。虽然下面这个经常令人不快的例子需要更多的努力来实现和维护(关于为这段代码编写的行数,因为每个公开可用的继承函数都必须由继承类显式调用),使用上面描述的删除在很多方面都非常相似:

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 */ }
};

提前感谢您的回答,并进一步了解这个潜在的删除用例。

4

3 回答 3

11

C++ 标准的第 8.4.3/2 段间接禁止删除覆盖虚函数的函数:

“除了声明之外,隐式或显式引用已删除函数的程序格式错误。[注意:这包括隐式或显式调用函数并形成指向函数的指针或指向成员的指针”

通过指向基类的指针调用重写虚函数是试图隐式调用该函数。因此,根据 8.4.3/2,允许这样做的设计是非法的。另请注意,没有符合 C++11 的编译器允许您删除覆盖的虚函数。

更明确地说,第 10.3/16 段也规定了同样的要求:

“具有已删除定义 (8.4) 的函数不应覆盖没有已删除定义的函数。同样,没有已删除定义的函数不应覆盖已删除定义的函数。

于 2013-01-16T19:25:17.947 回答
6

10.3p16:

具有已删除定义的函数 (8.4) 不应覆盖没有已删除定义的函数。同样,没有删除定义的函数不应覆盖具有删除定义的函数。

其他答案很好地解释了为什么,但是你有官方的 Thou Shalt Not。

于 2013-01-16T20:08:48.140 回答
3

考虑一些功能:

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

这将在 LibraryVersion2 甚至被编写之前编译。

所以现在你用删除的虚拟实现 LibraryVersion2。

f 已经编译好了。直到运行时它才知道调用了哪个 LibraryVersion1 子类。

这就是为什么删除虚拟是不合法的,它没有任何意义。

你能做的最好的事情是:

class LibraryVersion2 : public LibraryVersion1
{
public:
    virtual void doSomething1() override
    {
         throw DeletedFunctionException();
    }
}
于 2013-01-16T19:25:08.913 回答