什么时候应该重新定义非虚拟方法? 虚拟< /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:if
Base::foo
返回指向
Y
对象的指针,为什么它的签名中没有该指针?如果没有,则<代码> STATICECAST 是错误的。现在考虑<代码> x*f(Base&