【问题标题】:A virtual member function is used if it is not pure?如果它不是纯的,则使用虚拟成员函数?
【发布时间】:2011-05-07 22:49:34
【问题描述】:

C++03 3.2.2 ...如果对象或非重载函数的名称出现在可能求值的表达式中,则使用该对象或非重载函数。如果不是纯的,则使用虚成员函数...

然后在3.2.3 中,我们有: 每个程序都应包含该程序中使用的每个非内联函数或对象的一个​​定义;无需诊断。定义可以显式出现在程序中,可以在标准或用户定义库中找到,或者(在适当时)隐式定义(参见 12.1、12.4 和 12.8)。 内联函数应在使用它的每个翻译单元中定义。

按照我正在阅读的内容:不使用纯虚函数。 ODR 仅适用于使用的功能。这是否意味着以下内容是合法的?我猜答案是否定的,它没有,但我不明白为什么。

//x.h
struct A
{
   virtual void f() = 0;
};

//y.cpp
#include "x.h"
void A::f()
{
}

//z.cpp
#include "x.h"
#include <iostream>
void A::f()
{
   std::cout << "Hello" << std::endl;
}

//main.cpp
#include "x.h"
struct B:A
{
   virtual void f()
   {
      A::f();
   }
};

int main()
{
   A* p = new B;
   p->f();
}

【问题讨论】:

  • 这是否编译/链接?我觉得会有一个链接错误,因为重复的 struct A 实现。
  • @Rolland:你什么意思?类定义可能而且应该出现在使用该类的每个翻译单元中......
  • @Stephane Rolland:再次阅读标准:“每个程序都应包含该程序中使用的每个非内联函数 [...] 的确切定义”和“虚拟成员函数是如果它不纯,则使用”。这意味着如果一个虚成员函数是纯的,标准可能会被解释为不使用它,这将允许任意多个定义。
  • GCC 链接器确实给出了一个错误(即使没有main.cpp),但是从标准中看不清楚甚至是错误的,这取决于if 的含义。
  • @Stephanie:我想我们都同意这个例子违背了标准的意图,它确实会引起问题,这很好。讨论是关于标准的措辞是否意外忘记涵盖这种情况。

标签: c++ pure-virtual language-lawyer one-definition-rule


【解决方案1】:

这两个子句并不相互排斥。 如果虚函数不是纯函数,则使用它并不意味着反之亦然。如果一个虚函数是纯的,并不意味着它一定不会被使用。 “如果它的名称出现在可能评估的表达式中”仍然可以使用它,例如在您的示例中:A::f();

【讨论】:

  • 呃...我仍然不相信。使用的术语是通过“潜在评估表达式”的概念定义的,纯虚函数有一个例外。所以 A::f() 和 x->f(); 一样是一个潜在的计算表达式;但后者并不意味着使用该功能...
  • 3.2p2 没有意义,除非读起来好像满足任何单独的要求就使用了一个符号。例如,分配函数由 new 表达式使用。但如果使用operator new 显式调用,它当然也可以使用。
  • @Armen Tsirunyan:再次仔细阅读条款。纯虚函数也不例外;例外情况是自动使用的非纯虚函数,无论它们是否也以任何其他方式使用
  • @Armen Tsirunyan:是的。有一个实际原因:在许多 C++ 实现中,需要将此类函数的地址放入从不覆盖该函数的类派生的任何对象的 vtable 中。这样的类可能只在稍后编译的另一个翻译单元中定义,因此如果这样的函数在所有情况下都被计为 used ,那么对于实现来说是最简单的,因此实现可以依赖于能够找到该函数在链接时。纯虚函数必须在任何可实例化的类中被覆盖,所以这不是他们关心的问题。
  • @Armen 对p-&gt;f() 有意见。这实际上会使纯虚函数被使用,但当然不是这样。但是,open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1174 已经有一个关于此的问题报告。
【解决方案2】:

此代码违反了 ODR。 A::f 是多重定义的。因此它有 UB。

根据 $3.2/5 仅允许跨翻译单元的多个定义

可以有多个定义 类类型(第 9 条), 枚举类型 (7.2),内联 外联功能 (7.1.2),类模板(第 14 条), 非静态函数模板(14.5.5), 类模板的静态数据成员 (14.5.1.3),类的成员函数 模板 (14.5.1.1) 或模板 一些模板的专业化 未指定参数(14.7, 14.5.4) 在程序中,前提是每个定义以不同的形式出现 翻译单位,并提供 定义满足以下 要求。

【讨论】:

    【解决方案3】:

    正如@Charles Bailey 所指出的,您的A::f 实际上已被使用,即使它是纯虚拟的。但这不是重点。

    单一定义规则不适用于未使用的函数是不准确的。我们有:

    3.2p1 任何翻译单元不得包含一个以上的任何变量、函数、类类型、枚举类型或模板的定义。

    3.2p3 每个程序都应包含该程序中使用的每个非内联函数或对象的一个​​定义;无需诊断。

    这些要求似乎意味着一个使用过的函数必须只有一个定义,而一个未使用的函数(包括一个从未显式调用的纯虚函数)可能没有定义,也可能只有一个定义。在任何一种情况下,非内联函数的多个定义都会导致程序格式错误。

    至少,我很确定这就是意图。但是你可能会在措辞上遇到一个漏洞,因为一个非常字面的阅读并没有说明在不同的翻译单元中对同一个未使用函数的多个不同定义是错误的。

    // x.cpp
    void f() {}
    void g() {}
    
    // y.cpp
    #include <iostream>
    void f() {
      std::cout << "Huh" << std::endl;
    }
    void h() {}
    
    // z.cpp
    void g();
    void h();
    int main() { 
      g();
      h();
      return 0;
    }
    

    【讨论】:

    • 不用等,3.2p1 确实说“翻译单元”。我认为实际上可能存在措辞漏洞。
    • 3.2/1 不是措辞漏洞。它适用于允许每个翻译单元定义一次的内联函数、类和枚举,与非内联函数和对象不同。
    • @Charles:同意。但是什么要求说我的示例程序格式错误?第 1 段不适用,因为定义在不同的翻译单元中。 P2 没有说使用了f()。 P3 不适用,因为它没有被使用。 P4 是关于类定义的。 P5 是关于包括真正可以多重定义的内联函数在内的东西。
    • 我认为 supposed 应该是 3.2/5,它准确地描述了哪些实体可能在程序中具有多个定义,以及多重定义的限制。我承认这只是暗示,如果实体不在列表中,它在程序中可能没有多个定义(即只有零个或一个定义)。
    • 我前段时间在usenet上发过这个,发现是个缺陷。但我没有将其作为问题报告发布给委员会。您可能希望这样做。
    【解决方案4】:

    这是相关但偏离主题:从引用看来,标准中似乎有一个漏洞,好吧:它还应该说使用了纯虚拟析构函数,并且必须定义它;至少如果存在任何被销毁的派生类对象,或者如果定义了此类对象的析构函数,因为派生类析构函数必须调用基析构函数,它隐含地使用qualified::id 语法这样做。这种析构函数的定义通常是微不足道的,但不能省略,也不能生成。

    【讨论】:

    • 12.4p7: "析构函数可以声明为virtual (10.3) 或纯virtual (10.4);如果在程序中创建了该类或任何派生类的任何对象,则析构函数应予以定义。” (我的注释:“对象”包括成员子对象。)
    【解决方案5】:

    [class.abstract]:“纯虚函数仅在使用或如同使用 (12.4) 限定 ID 语法 (5.1) 调用时才需要定义。”

    您的A::fB::f 调用,因此A::f 必须有一个定义。

    【讨论】:

    • 我同意根据您的报价必须有 A::f 的定义。但我没有看到哪个子句需要有一个 single 定义
    • @Armen Tsirunyan:请参阅我的回复以进行澄清
    猜你喜欢
    • 1970-01-01
    • 2010-11-08
    • 1970-01-01
    • 1970-01-01
    • 2019-10-05
    • 2010-09-21
    • 1970-01-01
    • 2011-03-31
    • 1970-01-01
    相关资源
    最近更新 更多