C++ 风格:为覆盖方法添加前缀 virtual 关键字
Posted
技术标签:
【中文标题】C++ 风格:为覆盖方法添加前缀 virtual 关键字【英文标题】:C++ Style: Prefixing virtual keyword to overridden methods 【发布时间】:2010-11-25 03:39:45 【问题描述】:我一直在与我的同事讨论是否在被覆盖的方法前加上 virtual 关键字,或者只在原始基类中添加前缀。
我倾向于在所有虚拟方法(即涉及 vtable 查找的方法)前加上 virtual 关键字。我的理由有三个:
鉴于 C++ 缺少覆盖 关键字,虚拟的存在 关键字至少会通知您 该方法涉及查找和 理论上可以被覆盖 进一步的专业化,或可能是 通过指向更高级别的指针调用 基类。
一直使用这种风格 意味着,当你看到一个方法时 (至少在我们的代码中)没有 virtual 关键字,你可以 最初假设它既不是 源自基础或专业 在子类中。
如果由于某些错误, virtual 已从 IFoo 中删除,所有 孩子们仍然可以正常工作 (CFooSpecialization::DoBar 会 仍然覆盖 CFooBase::DoBar, 而不是简单地隐藏它)。
据我了解,反对这种做法的论点是,“但该方法不是虚拟的”(我认为这是无效的,并且源于对虚拟性的误解),以及“当我看到 virtual 关键字时,我希望这意味着有人从中派生,然后去寻找他们。”
假设的类可能分布在多个文件中,并且有几个专业化。
class IFoo
public:
virtual void DoBar() = 0;
void DoBaz();
;
class CFooBase : public IFoo
public:
virtual void DoBar(); // Default implementation
void DoZap();
;
class CFooSpecialization : public CFooBase
public:
virtual void DoBar(); // Specialized implementation
;
从风格上讲,您会从两个派生类中删除 virtual 关键字吗?如果是这样,为什么? Stack Overflow 对此有何想法?
【问题讨论】:
我很想看看 Bjarne(或 C++ 委员会)为什么允许在派生类中省略 virtual 的理由。这个理由可能比你的同事提供了更令人信服的理由来实际省略它。不过,也许只是在某些情况下。 不幸的是,规范中没有包含任何理由。它只是说它是“合法但多余(具有空语义)”。不过,如果有任何关于理由的记录,听听会很有趣。 我没有“设计与进化”的副本,它可能包含也可能不包含任何内容。 我想指出,从 C++11 开始,您还可以使用override
说明符来指定一个函数是虚函数并覆盖另一个函数;这样做还允许编译器在函数没有覆盖任何内容时向您提供错误消息。以您的示例为例,CFooBase
和 CFooSpecialization
将具有 virtual void DoBar() override;
。
@JustinTime 我认为人们想要的是一种标准强制的方式来要求描述性vfunc声明,而不是记住故意输入另一个完全可选的关键字,无需打开依赖于编译器的警告。但我认为现在就它会破坏什么以及会导致的习惯性反弹而言为时已晚。也就是说,这是g++
的诀窍:***.com/questions/29145476/…
【参考方案1】:
一个虚函数永远是虚函数。
因此,无论如何,如果在后续类中未使用 virtual 关键字,它不会阻止函数/方法成为“虚拟”,即被覆盖。因此,我参与的其中一个项目有以下我有点喜欢的指导方针:
如果函数/方法应该 被覆盖总是使用 “虚拟”关键字。这尤其 在接口/基础中使用时为真 类。 如果派生类应该 进一步明确地细分 为每个声明“虚拟”关键字 可以的功能/方法 被覆盖。 C++11 使用“覆盖”关键字 如果派生的函数/方法 类不应该是 再次子分类,然后是关键字 'virtual' 将被评论 表示函数/方法 被覆盖但没有 覆盖它的其他类 再次。这当然不妨碍 有人从压倒一切 派生类,除非该类 是最终的(不可推导的),但它 表示该方法不应该是 被覆盖。 例如:/*virtual*/ void guiFocusEvent();
C++11,使用'final'关键字和'override'
例如:void guiFocusEvent() override final;
【讨论】:
【参考方案2】:注意:我的回答是关于我们中的一些人仍然坚持使用的 C++03。 C++11 有 override
和 final
关键字,正如 @JustinTime 在 cmets 中所建议的那样,可能应该使用它来代替以下建议。
已经有很多答案和两个最突出的相反意见。我想将@280Z28 在他的回答中提到的内容与@StevenSudit 的意见和@Abhay 的风格指南结合起来。
我不同意 @280Z28 并且不会使用 Microsoft 的语言扩展,除非您确定只会在 Windows 上使用该代码。
但我确实喜欢这些关键字。那么,为什么不直接使用#define-d 关键字添加来清楚起见呢?
#define OVERRIDE
#define SEALED
或
#define OVERRIDE virtual
#define SEALED virtual
不同之处在于您决定在您在第 3 点概述的情况下要发生什么。
3 - 如果由于某些错误,从 IFoo 中删除了 virtual,所有子级仍将起作用(CFooSpecialization::DoBar 仍将覆盖 CFooBase::DoBar,而不是简单地隐藏它)。
虽然我认为这是一个编程错误,所以没有“修复”,你可能甚至不应该费心去缓解它,但应该确保它崩溃或以其他方式通知程序员(虽然我不认为一个)。
如果您选择了第一个选项并且不喜欢添加#define
,那么您可以使用 cmets,例如:
/* override */
/* sealed */
这应该适用于您希望清晰的所有情况,因为我认为 virtual 这个词对于您想要的内容来说不够清晰做。
【讨论】:
只需要指出两点:1)override
和 sealed
在参数列表之后,而不是在开头。 void func() virtual /* ... */
不是有效函数,但 void func() override /* ... */
是,只要它覆盖基类中的虚函数。 2) 从 C++11 开始,override
是 ISO C++ 中的实际说明符,与 Microsoft 特定版本具有相同的语义,final
也是 ISO C++11 说明符(据我所知)与 Microsoft 特定的 sealed
语义相同。
@JustinTime 考虑到这个问题是在 2009 年提出的,我的答案是 C++03,有些人仍在使用它。我很清楚 C++11 确实提供了您提到的关键字,但我认为它尚未适用于所有 C++ 社区。但是,您的评论确实指出我可能需要为我的回答添加序言。
@JustinTime 另外,在我的回答中,您在哪里发现我建议将 virtual 关键字放在参数列表之后?我真的不明白你的观点 1) 来自哪里......
那将是 #define OVERRIDE virtual
和 #define SEALED virtual
宏。如果您使用这些定义,并像使用 override
和 final
或旧的 Microsoft 特定的 override
和 sealed
一样使用 OVERRIDE
和 SEALED
,那么预处理器将转向,例如,@987654342 @变成void func() virtual /* ... */
。
另外,C++11 没有添加上下文相关关键字sealed
,它添加了final
,据我所知,它的语义与较旧的 Microsoft 特定 sealed
。【参考方案3】:
我能想到一个缺点: 当一个类成员函数没有被覆盖并且你将它声明为虚拟时,你会在该类定义的虚拟表中添加一个不必要的条目。
【讨论】:
如果基本方法已经被覆盖,你已经受到了惩罚。将覆盖的函数标记为 virtual 不会添加任何内容。这纯粹是给程序员的说明。【参考方案4】:无论哪种方式,添加virtual
都不会产生重大影响。我倾向于更喜欢它,但这确实是一个主观问题。但是,如果您确保使用override
and sealed
keywords in Visual C++,您将在编译时捕获错误的能力得到显着提高。
我在我的 PCH 中包含以下几行:
#if _MSC_VER >= 1400
#define OVERRIDE override
#define SEALED sealed
#else
#define OVERRIDE
#define SEALED
#endif
【讨论】:
很好 - 我不知道 MSVC 对本机 C++ 代码有此功能。我可能会做与您所做的类似的事情(尽管我怀疑我会被团队覆盖 - “那不是 C++!”)。 谢谢,我在这里学到了一些新东西!但是,我很想说我不想使用非标准语言扩展,除非我绝对必须这样做。#define
-magic 似乎可以使代码跨平台编译,但是当您在 Windows 之外的代码库上工作时,它可能不会限制错误蔓延到其中。因此没有投票赞成或反对。
你现在可以(至少在理论上)摆脱你的#define 魔法。 override
现在是标准的 (C++11),您可以将 sealed
替换为 final
(也是 C++11)。
或者如果您仍然需要使用旧版本的 Visual Studio,您可以重写它,使其默认将SEALED
定义为final
,但将其设置为sealed
用于所述旧版本VS。【参考方案5】:
我倾向于不使用编译器允许我省略的任何语法。话虽如此,C# 的部分设计(试图改进 C++)是要求将虚拟方法的覆盖标记为“覆盖”,这似乎是一个合理的想法。我担心的是,因为它完全是可选的,所以有人忽略它只是时间问题,到那时你就会养成期望覆盖被指定为“虚拟”的习惯。那么,也许最好只生活在语言的限制内。
【讨论】:
“我倾向于不使用编译器允许我省略的任何语法。” - 虽然我认为有一些强有力的案例可以使用我允许省略的关键字,以便清楚起见。例如,如果我将类成员分组在一起,那么我会在每个组的开头放置一个访问说明符,即使它与前一个组的说明符相同。我将“虚拟”放在同一类别中——在我的经验中,错过某些东西是虚拟的是如此普遍,以至于添加它的价值超过了成本。这实际上只是一个评论,前提是该基地确实是(并且仍然是)虚拟的。 这是否意味着您不使用“inline”、“const”、“volatile”和“explicit”?请注意,我不会把老狗“auto”和“register”拿出来(不记得他们是否甚至从 C 过渡到 C++,也许不是,但没关系,我从来没有在反正20多年了)。如果您的意图是“我不使用任何对代码没有影响的语法”,那就有点不同了。不想学究气。 我不断听到很多人说 C# 是对 C++ 的改进。我想这是一个完全不同的问题,但是如果一种语言明显更复杂、有更多的语法结构(有人用 LINQ 吗?)并且与它的标准库紧密相关(我花了相当长的时间),它怎么能成为一种改进, 来自 C++, 理解 int[] 是一个继承自 IEnumerable我完全同意你的理由。这是一个很好的提醒,该方法在调用时将具有动态调度语义。您的同事正在使用的“该方法不是虚拟的”论点完全是虚假的。他混淆了虚拟和纯虚拟的概念。
【讨论】:
以上是关于C++ 风格:为覆盖方法添加前缀 virtual 关键字的主要内容,如果未能解决你的问题,请参考以下文章
C ++迭代器取消引用和前缀递增/递减样式? *--Iter ok 风格是不是明智?