【问题标题】:Applying "using" keyword on C++ pure virtual function在 C++ 纯虚函数上应用“使用”关键字
【发布时间】:2019-05-31 00:33:08
【问题描述】:

B 类重写了 A 类的纯虚函数“print()”。C 类继承了 B 类,并具有“使用 A::print”语句。 现在为什么 C 类不是抽象类?

class A {
    public :
        virtual void print() =0;
};

class B:public A {
    public:
        void print();
};

void B :: print() {

    cout << "\nClass B print ()";
}

class C : public B {

    public:
        using A::print;
};

void funca (A *a) {

    // a->print(1);                    
}

void funcb (B *b) {

    b->print();         
}

void funcc (C *c) {

    c->print();             
}

int main() {
    B b;
    C c;        
    funca(&c);              
    funcb(&c);              
    funcc(&c);              
    return 0;               
}

输出:

    Class B print ()
    Class B print ()

【问题讨论】:

标签: c++ overriding virtual-functions using-declaration


【解决方案1】:

根据我第一次尝试找到答案,@Oliv 的 cmets 和答案,让我尝试总结 C 内的 using A::memberFct 声明的所有可能场景。

  • A 的成员函数是虚拟的,并被 B 覆盖
  • A 的成员函数是非虚拟的,被 B 隐藏
  • A 的成员函数是非虚拟的,被 C 本身隐藏

这些情况的一个小例子如下。

struct A {
   virtual void f() {}
   void g() {}
   void h() {}
};

struct B : A {
   void f() override {}
   void g() {}
};

struct C : B {
   using A::f; // Virtual function, vtable decides which one is called
   using A::g; // A::g was hidden by B::g, but now brought to foreground
   using A::h; // A::h is still hidden by C's own implementation
   void h() {}
};

通过C的接口调用所有三个函数会导致不同的函数调用:

C{}.f(); // calls B::f through vtable
C{}.g(); // calls A::g because of using declarative
C{}.h(); // calls C::h, which has priority over A::h

请注意,在类中使用声明的影响有限,即它们会更改名称查找,但不会更改虚拟调度(第一种情况)。成员函数是否是纯虚函数不会改变这种行为。当基类函数被继承层次结构中的函数隐藏时(第二种情况),会调整查找,以便使用声明的主题具有优先权。当基类函数被类本身的函数隐藏时(第三种情况),类本身的实现具有优先权,见 cppreference:

如果派生类已经有一个具有相同名称、参数列表和限定条件的成员,则派生类成员隐藏或覆盖(不冲突)从基类引入的成员。

在您原来的 sn-p 中,C 因此不是抽象类,因为只有相关成员函数的查找机制受 using 声明的影响,并且 vtable 点不指向纯虚拟成员函数实现。

【讨论】:

  • 那是错误的,derived class already has a member,表示在这个类中首先声明的成员。在此处查看演示:godbolt.org/z/ff5cEb
  • @Oliv 不确定我是否明白你的意思,为什么它的行为与所讨论的成员函数的虚拟性不同?如果隐藏 覆盖两者导致排除 using 声明引入的声明集,它不应该表现出相同的行为吗?
  • 您的权利也不适用于本案。您网站的标准段落也不是...
  • 我已经找到了解释。你怎么看?
【解决方案2】:

这是因为 using 声明不会引入新成员或新定义。相反,它引入了一组声明,可以通过限定名称查找[namespace.udecl]/1

using-declaration 中的每个 using-declarator,将一组声明引入到 using-declaration 出现的声明性区域中。通过对 using-declarator 中的名称执行限定名称查找 ([basic.lookup.qual], [class.member.lookup]) 来找到 using-declarator 引入的声明集,不包括如所述隐藏的函数下面。

它只对通过限定名称查找找到的实体有影响。因此,它对 final overrider [class.virtual]/2:

的定义没有影响

[...] 类对象 S 的虚成员函数 C::vf 是最终覆盖器,除非其中 S 是基类子对象(如果有)的最派生类 ([intro.object]) 声明或继承另一个覆盖 vf 的成员函数。

其含义与以下内容不同:最终覆盖者是由表达式 D::vf 指定的实体,其中 D 是派生度最高的类,而 S 是其基类子对象。

因此,它不会影响一个类是否为抽象类[class.abstract]/4

一个类是抽象的,如果它包含或继承了至少一个最终覆盖器是纯虚拟的纯虚函数。


注1:

结果是 using 指令将导致非虚函数和虚函数的不同行为 [expr.call]/3:

如果选择的函数是非虚函数,或者类成员访问表达式中的 id-expression 是限定 id,则调用该函数。 否则,在对象表达式的动态类型中调用它的最终覆盖器;这种调用称为虚函数调用。

简单地说:

  • 非虚函数 => 通过限定名查找找到的函数
  • 虚函数 => 调用最终覆盖器

所以如果print 不是虚拟的:

class A {
  public :
  void print() {
    std::cout << "\n Class A::print()";
    }
  };

int main() {
  B b;
  C c;        
  b.print() // Class B print ()
  c.print() // Class A print ()
  //Equivalent to:
  c.C::print() // Class A::print()             
  return 0;               
  }

注2:

正如一些人在前面的标准段落中可能已经注意到的那样,可以执行一个合格的虚函数调用来获得非虚行为。所以虚函数的 using 声明可能是实用的(可能是一种不好的做法):

class A {
  public :
  virtual void print() =0;
  };

//Warning arcane: A definition can be provided for pure virtual function
//which is only callable throw qualified name look up. Usualy an attempt
//to call a pure virtual function through qualified name look-up result
//in a link time error (that error message is welcome).
void A::print(){ 
  std::cout << "pure virtual A::print() called!!" << std::endl;
  }

int main() {
  B b;
  C c;        
  b.print() // Class B print ()
  c.print() // Class B print ()
  c.C::print() // pure virtual A::print() called!!
  //whitout the using declaration this last call would have print "Class B print()"              
  return 0;               
  }

Live demo

【讨论】:

  • 也许评论一下using A::f如何影响对非虚拟隐藏成员函数f的查找可能会有所帮助?无论如何,伟大的标准挖掘!
  • @lubgr 我做到了,我给出的例子很纯粹,可能令人困惑。
  • 啊,抱歉没说清楚,实际上我的意思是the behavior you linked earlier - 如果A::print-虚拟的,那么C 中的using A::print 确实如此确实导致通过C 调用A::print
  • @lubgr 啊啊啊啊啊!
  • 参见 [namespace.udecl]/2 “每个 using-declaration 都是一个声明 [...]”,这与您的主张“因此 using 声明不是一个声明”直接矛盾。我认为您要说的是命名函数的使用声明不是函数声明等。
猜你喜欢
  • 1970-01-01
  • 2016-09-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多