C++11删除重写方法
Posted
技术标签:
【中文标题】C++11删除重写方法【英文标题】:C++11 Delete overridden Method 【发布时间】:2012-12-31 04:44:04 【问题描述】:前言:
这是一个关于 C++11 中引入的删除运算符的新含义的最佳实践问题,该运算符应用于覆盖继承的父级虚拟方法的子类。
背景:
根据标准,引用的第一个用例是明确禁止调用某些类型的函数,否则转换将是隐式的,例如最新 C++11 standard draft 的第 8.4.3 节中的示例:
struct sometype
sometype() = delete; // OK, but redundant
some_type(std::intmax_t) = delete;
some_type(double);
;
上面的例子是明确的和有目的的。但是,以下示例通过将新运算符定义为已删除来覆盖并阻止调用它,这让我开始思考我稍后在问题部分中确定的其他场景(下面的示例来自C++11 standard draft 的§8.4.3 ):
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 */
;
提前感谢您的回答和对删除的这种潜在用例的进一步了解。
【问题讨论】:
我认为virtual void doSomething1() override = delete;
不合法。你想让((LibraryVersion1*)(new LibraryVersion2))->doSomething1()
做什么?
根据我的理解,即使强制转换为 LibraryVersion1,删除的函数仍会尝试在 LibraryVersion2 覆盖的情况下被调用,并导致代码无法编译。正如我的问题所述,这就是“is-a”关系被破坏的地方,但它肯定会按预期强制弃用。
新鲜:open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3485.pdf
我不认为将虚函数声明为已删除 C++11 是合法的。 Clang 和 GCC 都不允许这样做
@statueuphemism:针对LibraryVersion1*
编译的代码对LibraryVersion2
一无所知,那么它如何静态(在编译时)知道它会在将来的某个日期被删除一些子类。虚函数是动态调度的。也许它可能会引发运行时异常,但您可以通过使用 throw DeprecatedFunctionException
覆盖函数来做到这一点,因此不值得将其作为语言功能。
【参考方案1】:
C++ 标准的第 8.4.3/2 段间接禁止删除覆盖虚函数的函数:
“除了声明之外,隐式或显式引用已删除函数的程序是格式错误的。[注意:这包括隐式调用函数 或显式地形成指向函数的指针或成员指针"
通过指向基类的指针调用重写虚函数是尝试隐式调用该函数。因此,根据 8.4.3/2,允许这样做的设计是非法的。另请注意,没有符合 C++11 的编译器允许您删除覆盖的虚函数。
更明确地说,第 10.3/16 段也有同样的规定:
“具有已删除定义 (8.4) 的函数不应覆盖没有已删除定义的函数。同样,没有已删除定义的函数不应覆盖已删除定义的函数。 "
【讨论】:
谢谢。我不知何故错过了那颗宝石。刚刚找到另一个直接禁止它的引用:“具有删除定义的函数(8.4)不应覆盖没有删除定义的函数。同样,没有删除定义的函数不应覆盖具有删除定义的函数已删除定义。” @statueuphemism:对,好点。让我把它整合到我的答案中【参考方案2】:10.3p16:
具有已删除定义的函数 (8.4) 不应覆盖没有已删除定义的函数。同样,没有删除定义的函数也不应覆盖定义已删除的函数。
其他答案很好地解释了为什么,但是你有官方的 Thou Shalt Not。
【讨论】:
哦,您已经在对另一个答案的评论中引用了这一点。那好吧。【参考方案3】:考虑一些功能:
void f(LibraryVersion1* p)
p->doSomething1();
这将在编写 LibraryVersion2 之前编译。
所以现在你用删除的虚拟实现 LibraryVersion2。
f 已经被编译。直到运行时它才知道调用了哪个 LibraryVersion1 子类。
这就是为什么删除的虚拟是不合法的,没有任何意义。
你能做的最好的就是:
class LibraryVersion2 : public LibraryVersion1
public:
virtual void doSomething1() override
throw DeletedFunctionException();
【讨论】:
如果我有这样做的声誉,我也会对此表示赞同。不过,关于“你能做的最好的”补充,在运行时抛出一个异常来表示一个方法的弃用对我来说在弃用一个函数时似乎很混乱(我当然不想使用一个这样做的库)。我认为这种情况在那时会乞求重新设计。但你是对的,这可能是该语言可以为重写方法的删除定义强制执行的最佳方法。 不清楚您要解决的问题是什么。您似乎想要一种管理库版本控制的方法。所以首先编写库 1,然后使用库 1 编写应用程序 1,然后编写库 2,并且您希望应用程序 1 继续使用库 2。 我的例子有点做作,我目前并没有真正尝试解决问题。我认为这个想法是一种潜在的方法,可以简单地维护两个软件,其中一个软件直接从另一个软件继承,而不需要任何额外的合并(有点像 Python 2.7,它与 Python 3.0+ 有一些共性,但仍在维护中由于存在显着差异)。如果您要维护一个继承自 Python2_7 的 Python3_0 类,则共享代码中的错误修复将自动传播。不过,还有其他更好的设计可以实现这一点。以上是关于C++11删除重写方法的主要内容,如果未能解决你的问题,请参考以下文章
如何从 url 中删除 id 和 title 并用域后跟标题名称重写它?
如果 url 扩展名为 .php,则 htaccess 重写将其删除