是不是曾经;道德",;覆盖非虚拟函数? 我已经使用了下面的C++经验法则:

是不是曾经;道德",;覆盖非虚拟函数? 我已经使用了下面的C++经验法则:,c++,virtual-functions,C++,Virtual Functions,如果类在其 基类,函数应该是 在基础中声明virtual 我想我遇到了一个例外。为了判断这是否合理,或者指出我设计中的一个缺陷,我要问这个问题。我想得到一些例子或更好的规则 编辑:我试着在这里描述我的用例,我明白我并不真的需要继承 不过我想问一个一般性的问题。谢谢你的回答 不能重写非虚拟函数。您唯一能做的就是隐藏基类实现。但这并没有提供虚拟函数提供的多态行为 可能吗?对 这是否道德?这取决于你对道德的定义。 这会让您的开发伙伴和未来的维护程序员感到困惑吗?绝对是 在派生类中使用与基类中的非虚函

如果类在其 基类,函数应该是 在基础中声明
virtual

我想我遇到了一个例外。为了判断这是否合理,或者指出我设计中的一个缺陷,我要问这个问题。我想得到一些例子或更好的规则


编辑:我试着在这里描述我的用例,我明白我并不真的需要继承

不过我想问一个一般性的问题。谢谢你的回答

不能重写非虚拟函数。您唯一能做的就是隐藏基类实现。但这并没有提供虚拟函数提供的多态行为

可能吗?对 这是否道德?这取决于你对道德的定义。 这会让您的开发伙伴和未来的维护程序员感到困惑吗?绝对是

在派生类中使用与基类中的非虚函数同名的函数,只会隐藏基类函数及其重载


我认为这是对继承的滥用,因为您基本上是说您希望以基类契约不允许的方式重新定义某些事情的完成方式。通过使函数在基类中成为非虚拟函数,您可以指定希望函数执行的操作(其接口),更重要的是指定希望函数执行操作的方式(其实现)。将函数设置为非虚拟函数的含义是,在派生类中,其接口和实现都不应更改。

我认为您错误地记住了该规则。规则是:“如果要从基类重写虚拟方法,则应将重写方法声明为虚拟。”


这是一个代码风格的规则,可以防止混淆,因为虚拟修饰符是继承的。

我个人不喜欢这样,但有时它很有用。标准库也使用此功能:

stringstream ss;

/* Imagine you want to redirect all output that goes into "ss"
 * to "cout". The following does NOT work! */
ss.rdbuf(cout.rdbuf());
为什么它不起作用?因为
stringstream
隐藏了
ios::rdbuf
,该函数具有相同的命名功能,只提供对其内部
std::stringbuf
的读取访问,而不提供对附加缓冲区的读取访问。您需要执行以下操作

ss.std::ios::rdbuf(cout.rdbuf());
现在,附加到流的缓冲区不等于
ss.rdbuf()
返回的值。但我个人不喜欢这种设计


不过,我曾经很善于躲藏。在我看来,隐藏需要一个条件:

  • 行为应该是相同的
在我的例子中,我有一个这样的基类(虽然不是很接近,但它传达了这种情况)


这样,我就避免了我的
B
类的每个用户都需要包含Foo的头。只有B的cpp文件知道它依赖于“Foo”,必须这样做

你能行。但这可能不是你想要的。OOPs的主要原则之一是运行时多态性。在这种情况下,您可能无法使用它

检查以下代码。它试图使用基类型对象来处理超类型对象。但在非虚拟的情况下,它不起作用

我的预期产出是

In B::printNonV() 
In B::printV()
但是我有

In A::printNonV() 
In B::printV()

#包括
使用名称空间std;
甲级
{
公众:
void printnovs(){

cout我认为人们普遍认为隐藏基类功能不是一件好事,应该很少做。更令人惊讶的问题之一是,你真的破坏了多态行为(如所述)。下面是一个例子,说明为什么这会令人惊讶

struct Person {
    virtual std::string get_name() const = 0;
    void print_name(std::ostream& s) const { s << get_name() << std::endl; }
};
struct Fred: Person {
    virtual std::string get_name() const { return "Fred"; }
};
struct Barney: Person {
    virtual std::string get_name() const { return "Barney"; }
    void print_name(std::ostream& s) const { s << "Bam Bam" << std::endl; }
};
std::ostream& operator<<(std::ostream& s, Person const& p) {
    p.print_name(s);
    return s;
}

int main() {
    Fred fred;
    Barney barney;
    barney.print_name(std::cout);
    std::cout << fred << barney << std::endl;
    return 0;
}

隐藏基类会破坏以各种方式(通常是令人不快的方式)令人惊讶的实现。

从基类重载(而不是重写)非虚拟函数的一个示例是当您使用CRTP实现模拟动态绑定时:

// in the shared header
template <typename Derived>
struct GenericOSDetails {
    size_t preferred_character_size() {
        return 1; // we expect `char` to be the preferred character type
    }
    size_t preferred_string_length(size_t numchars) {
        return numchars * static_cast<Derived&>(*this).preferred_character_size();
    }
    // other functions that do considerably more useful things based on
    // the preferred character size and encoding.
};

// in the linux header
struct LinuxOSDetails : GenericOSDetails<LinuxOSDetails> {
    // we're happy with the defaults.
};

// in the windows header
struct WindowsOSDetails : GenericOSDetails<WindowsOSDetails> {
    // configure ourselves for "Unicode" vs non-Unicode builds.
    size_t preferred_character_size() {
        return sizeof(TCHAR);
    }
};
//在共享头中
模板
结构泛型详细信息{
大小\u t首选字符\u大小(){
返回1;//我们希望'char'是首选的字符类型
}
大小\u t首选\u字符串\u长度(大小\u t numchars){
返回numchars*static_cast(*this).preferred_character_size();
}
//基于
//首选的字符大小和编码。
};
//在linux头文件中
结构LinuxOSDetails:genericsdetails{
//我们对默认设置感到满意。
};
//在windows标题中
结构WindowsOSDetails:GenericOSDetails{
//为“Unicode”与非Unicode版本配置我们自己。
大小\u t首选字符\u大小(){
返回大小f(TCHAR);
}
};
注意:模拟动态绑定-使用此技术,WindowsOSDetails的实例不会作为指向基类的指针传递,因此不需要虚拟函数。静态绑定始终使用,但基类仍然可以调用派生类函数并获取重载版本

老实说,我不确定这在实际中有多有用。可能99%的时候,你可能会考虑使用它。这要么是过早的优化,要么你应该提供一个策略作为模板参数,再加上一个常见情况下的默认策略,而根本不使用继承。但其余1%的时间,以及一般情况下l当您想使用继承但不想或不需要动态多态性时,如果您想避免使用虚拟函数,则可以避免使用虚拟函数


好吧,只有在模板密集的代码中,你才会对不依赖动态多态性的继承做任何特别有趣的事情。你的普通OOP范式不感兴趣。

IMHO,我想如果有理由“隐藏”,你知道你在做什么,为什么不?为什么不告诉我们为什么你认为你的案子是个例外?然后我们可以说如果我们同意,如果不同意,w
#include <iostream>

using  namespace std;

class A
{

    public:
    void printNonV(){
      cout<<"In A::printNonV() "<<endl;
    }

    virtual void printV(){
      cout<<"In A::printV()"<<endl;
   }
};


class B:public A
{

    public:
    void printNonV(){
      cout<<"In B::printNonV()"<<endl;
    }

    virtual void printV(){
      cout<<"In B::printV()"<<endl;
   }
};

int main(){
  A* b=new B();

  b->printNonV();
  b->printV();
}
struct Person {
    virtual std::string get_name() const = 0;
    void print_name(std::ostream& s) const { s << get_name() << std::endl; }
};
struct Fred: Person {
    virtual std::string get_name() const { return "Fred"; }
};
struct Barney: Person {
    virtual std::string get_name() const { return "Barney"; }
    void print_name(std::ostream& s) const { s << "Bam Bam" << std::endl; }
};
std::ostream& operator<<(std::ostream& s, Person const& p) {
    p.print_name(s);
    return s;
}

int main() {
    Fred fred;
    Barney barney;
    barney.print_name(std::cout);
    std::cout << fred << barney << std::endl;
    return 0;
}
Bam Bam
Fred
Barney
// in the shared header
template <typename Derived>
struct GenericOSDetails {
    size_t preferred_character_size() {
        return 1; // we expect `char` to be the preferred character type
    }
    size_t preferred_string_length(size_t numchars) {
        return numchars * static_cast<Derived&>(*this).preferred_character_size();
    }
    // other functions that do considerably more useful things based on
    // the preferred character size and encoding.
};

// in the linux header
struct LinuxOSDetails : GenericOSDetails<LinuxOSDetails> {
    // we're happy with the defaults.
};

// in the windows header
struct WindowsOSDetails : GenericOSDetails<WindowsOSDetails> {
    // configure ourselves for "Unicode" vs non-Unicode builds.
    size_t preferred_character_size() {
        return sizeof(TCHAR);
    }
};