什么时候应该重新定义非虚拟方法? 虚拟< /COD>方法是C++实现的一部分。除了避免与RTTI**和方法查找相关的开销外,是否有令人信服的理由省略virtual
假设什么时候应该重新定义非虚拟方法? 虚拟< /COD>方法是C++实现的一部分。除了避免与RTTI**和方法查找相关的开销外,是否有令人信服的理由省略virtual,c++,inheritance,virtual,C++,Inheritance,Virtual,假设virtual可以随时添加到基类中,那么重新定义非virtual方法的目的是什么 **它在现代CPU上是否可测量与这个问题无关。好吧,没有什么理由重新定义一个非虚拟函数。事实上,我建议不要这样做,因为在完全相同的对象上的相同函数调用可能会根据所使用的指针/引用的静态类型表现出不同的行为 重写虚拟成员函数允许您专门化派生类型的行为。重载非虚拟成员函数反而会提供一种替代行为,在这种行为中,哪种函数/行为将被执行对普通读者来说可能并不明显。好吧,没有什么理由重新定义一个非虚拟的函数。事实上,我建议
virtual
可以随时添加到基类中,那么重新定义非virtual
方法的目的是什么
**它在现代CPU上是否可测量与这个问题无关。好吧,没有什么理由重新定义一个非虚拟函数。事实上,我建议不要这样做,因为在完全相同的对象上的相同函数调用可能会根据所使用的指针/引用的静态类型表现出不同的行为
重写虚拟成员函数允许您专门化派生类型的行为。重载非虚拟成员函数反而会提供一种替代行为,在这种行为中,哪种函数/行为将被执行对普通读者来说可能并不明显。好吧,没有什么理由重新定义一个非虚拟的函数。事实上,我建议不要这样做,因为在完全相同的对象上的相同函数调用可能会根据所使用的指针/引用的静态类型表现出不同的行为
重写虚拟成员函数允许您专门化派生类型的行为。重载非虚拟成员函数将提供一种替代行为,在这种行为中,临时读者可能不清楚将执行哪些函数/行为。一种可能的用途是实现CRTP框架,其中定义了函数的默认版本:
#include <iostream>
//This could be any higher-order function.
template<typename T>
class CallFiveTimes {
protected:
void docalls() const {
for(int i(0); i != 5; ++i) static_cast<T const*>(this)->callme();
}
//Default implementation. If a lot
//of different functionality were required of `T`
//then defaults could make `T` easier to write.
void callme() const {
std::cout << "Default implementation.\n";
}
};
class Client : CallFiveTimes<Client> {
public:
void useFramework() {
docalls();
}
private:
friend struct CallFiveTimes<Client>;
//This redefinition will be used.
void callme() const {
std::cout << "Client implementation.\n";
}
};
class LazyClient : CallFiveTimes<LazyClient> {
public:
void useFramework() {
docalls();
}
friend struct CallFiveTimes<LazyClient>;
};
int main() {
Client c;
c.useFramework(); //prints "Client Implementation" five times
LazyClient lc;
lc.useFramework(); //prints "Default Implementation" five times
}
#包括
//这可以是任何高阶函数。
模板
类CallFiveTimes{
受保护的:
void docals()常量{
对于(int i(0);i!=5;++i)静态_cast(this)->callme();
}
//默认实现。如果很多
//“T”需要不同的功能`
//那么,默认值可能会使'T'更容易编写。
void callme()常量{
std::cout一种可能的用途是实现CRTP框架,其中定义了函数的默认版本:
#include <iostream>
//This could be any higher-order function.
template<typename T>
class CallFiveTimes {
protected:
void docalls() const {
for(int i(0); i != 5; ++i) static_cast<T const*>(this)->callme();
}
//Default implementation. If a lot
//of different functionality were required of `T`
//then defaults could make `T` easier to write.
void callme() const {
std::cout << "Default implementation.\n";
}
};
class Client : CallFiveTimes<Client> {
public:
void useFramework() {
docalls();
}
private:
friend struct CallFiveTimes<Client>;
//This redefinition will be used.
void callme() const {
std::cout << "Client implementation.\n";
}
};
class LazyClient : CallFiveTimes<LazyClient> {
public:
void useFramework() {
docalls();
}
friend struct CallFiveTimes<LazyClient>;
};
int main() {
Client c;
c.useFramework(); //prints "Client Implementation" five times
LazyClient lc;
lc.useFramework(); //prints "Default Implementation" five times
}
#包括
//这可以是任何高阶函数。
模板
类CallFiveTimes{
受保护的:
void docals()常量{
对于(int i(0);i!=5;++i)静态_cast(this)->callme();
}
//默认实现。如果很多
//“T”需要不同的功能`
//那么,默认值可能会使'T'更容易编写。
void callme()常量{
std::我可以问一下这是否是一本特定教科书中的问题吗?我只是好奇!你听说过CRTP吗?如果你不熟悉它,可以查一下:)@Yakk-不,这是我在开发过程中想到的。啊:那么作为一点补充信息,虚拟
函数开销在现代CPU上很容易测量。每个virtual
调用需要在虚拟
函数表中查找实例的类(这可能导致缓存未命中),然后跟随存储在该类中的指针到另一个地址。“随机”内存访问和指令跳转是每条指令中性能最差的。(这是对virtual
函数指针的合理有效实现的描述)。现在,大多数代码运行在性能不重要的环境中,但这是一个严重的打击。@Yakk-True。我只是不想让答案被RTTI的优缺点所左右,因此脚注:)我可以问一下这是否是一本特定教科书中的问题吗?我只是好奇!你听说过CRTP吗?如果你不熟悉它,请查阅:)@Yakk-不,这是我在开发过程中想到的。啊:那么作为一点补充信息,virtual
函数开销在现代CPU上很容易测量。每个virtual
调用需要在虚拟
函数表中查找实例的类(这可能导致缓存未命中),然后跟随存储在该类中的指针到另一个地址。“随机”内存访问和指令跳转是每个指令的最差性能命中。(这是对virtual
函数指针的合理有效实现的描述)。现在,大多数代码运行在性能不重要的环境中,但这是一个严重的打击。@Yakk-True。我只是不想让答案被RTTI的优缺点所左右,因此脚注:)void read(size\t n,void*bytes)
在base中,模板void read(size\t n,t*实例)
在child中--有点可疑,但是…?协变返回类型?(父级返回base
,child知道父级将持有派生的,并提供强制转换).CRTP,其中由于语法上的原因从CRTP实例调用派生实现(因此您不必每次在CRTP类中调用Foo()
时都限定self()->Foo()
)?@Yakk:如果不重写,就不能有协方差。在read
或CRTP的情况下,如果基函数名和派生类型相同,则会遇到我提到的问题:根据指针/引用的静态类型,可能会得到不同的函数。通过选择更好的/不同的命名您可以让用户更清楚、更简单。struct Base{X*foo();};struct-Derived:Base{Y*foo(){return static_cast(Base::foo();}
基本上是静态协方差。而且一点也不令人困惑。在某些情况下,CRTP也是如此——如果父对象将所有对foo
的调用静态转发给子对象,那么相同的名称不会引起混淆。@Yakk:ifBase::foo
返回指向Y
对象的指针,为什么它的签名中没有该指针?如果没有,则<代码> STATICECAST 是错误的。现在考虑<代码> x*f(Base&