使用声明作为替代

时间:2018-10-29 08:16:19

标签: c++ override language-lawyer multiple-inheritance using-declaration

我们在标准中有以下简单(并稍加修改以添加main和输出)示例:

struct A {
    virtual void f()
    {
        cout << "A\n";
    }
};

struct B : virtual A {
    virtual void f()
    {
        cout << "B\n";
    }
};

struct C : B, virtual A {
    using A::f;
};

int main()
{
    C c;
    c.f();              // calls B​::​f, the final overrider
    c.C::f();
    return 0;
}

从中我们可以得出结论,using A::f没有提供替代者。但是,标准中的措辞规定了什么?这是C ++ 17草案([class.virtual] p2)中最终替代程序的措辞:

  

<...>类对象S的虚拟成员函数C :: vf是最终的   覆盖程序,除非以S为基数的最派生类(4.5)   类子对象(如果有)声明或继承另一个成员   覆盖vf 的功能。在派生类中,如果是虚拟成员   基类子对象的功能具有多个最终重写器   该程序格式不正确。

我无法找到“替代”的实际含义。如果未定义它,并且我们认为任何声明都是重写器,则应该考虑using声明为重写器,因为[namespace.udecl] p2说:

  

每个using-declaration都是一个声明和一个成员声明,​​因此可以在类定义中使用。

我了解标准的意图,即使用声明不引入替代程序,但是有人可以指出实际使用标准语的引号吗?这是第一部分,现在到第二部分


考虑以下代码:

#include <iostream>
#include <string>

using std::cout;

class A {
public:
    virtual void print() const {
        cout << "from A" << std::endl;
    }
};

class B: public A {
public:
    void print() const override {
        cout << "from B" << std::endl;
    }
};

class C: public A {
public:
    void print() const override {
        cout << "from C" << std::endl;
    }
};

class D: public B, public C {
public:
    using C::print;
};

int main()
{
    D d{};
    d.print();
    return 0;
}

如果using声明未引入覆盖程序,则D中将有2个最终覆盖程序,因此-由于

而导致的未定义行为
  

在派生类中,如果是虚拟成员   基类子对象的功能具有多个最终重写器   该程序格式不正确。

对吗?

1 个答案:

答案 0 :(得分:6)

使用声明,尽管实际上是涉及声明性区域的声明,但不是功能声明。我们可以看到它是语法指定的:

  

[dcl.dcl]

     

1声明通常指定如何解释名称。   声明的格式为

declaration:
  block-declaration
  nodeclspec-function-declaration
  function-definition
  template-declaration
  deduction-guide
  explicit-instantiation
  explicit-specialization
  linkage-specification
  namespace-definition
  empty-declaration
  attribute-declaration

block-declaration:
  simple-declaration
  asm-definition
  namespace-alias-definition
  using-declaration
  using-directive
  static_assert-declaration
  alias-declaration
  opaque-enum-declaration

nodeclspec-function-declaration:
  attribute-specifier-seq declarator ;

并且在某种程度上在语义上。由于以下各段详细介绍了从基类引入成员函数的using声明与派生类中的成员函数声明有何不同。

  

[namespace.udecl]

     

15当using-declarator带来基类的声明时   成派生类,成员函数和成员函数模板   在派生类中重写和/或隐藏成员函数和成员   具有相同名称,parameter-type-list,   基本类别中的cv-qualification和ref-qualifier(如果有)   而不是冲突)。此类隐藏或覆盖的声明不包括在内   来自using-declarator引入的一组声明。

     

16为了解决过载,以下功能   通过using声明引入到派生类中被视为   尽管他们是派生类的成员。特别是   隐式地将此参数视为是指向的指针   派生类而不是基类。这对   函数的类型,以及在所有其他方面,函数   仍然是基类的成员。

请牢记这一点,如果您考虑到第一段的开头,请引用:

  

[class.virtual]

     

2如果在类Base中声明了虚拟成员函数vf   在直接或间接从Base派生的类中,   具有相同名称,参数类型列表的成员函数vf,   cv限定词和ref限定词(或不存在)   声明Base​::​vf,然后Derived​::​vf也是虚拟的   (无论是否声明),它都将覆盖Base​::​vf。对于   方便起见,我们说任何虚函数都可以覆盖自身。

我们可以看到它是一个虚拟的 function声明,它可以在基类中为虚拟函数引入重写器。而且,由于using声明不是函数声明,因此不符合条件。

当前措词部分来自CWG Defect 608。目的在于澄清该报告中的问题解释,并使用声明与虚函数重写器的概念进行解耦。


关于第二个问题,在该引号中要注意的重点是“基类 subobject 。您的代码示例在A中有两个 D子对象(该示例中的继承不是虚拟的)。每个人在BC中都有自己的最终替代项。因此,无论是否在D中声明另一个替代程序,该程序都不会格式错误。

您关注的段落适用于虚拟继承的情况。如果BC具有虚拟的A基址,并且D从两者继承而没有覆盖print,则程序would be ill-formed。同样由于上述原因,using C::print这样的using声明也无法使其格式正确。