8

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();
}
4

5 回答 5

11

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

于 2010-11-10T15:25:40.310 回答
3

此代码违反 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),前提是每个定义出现在不同的翻译单元中,并且定义满足以下要求。

于 2010-11-10T15:28:07.217 回答
1

正如@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;
}
于 2010-11-10T15:50:57.953 回答
1

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

于 2010-11-28T03:51:28.037 回答
0

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

YourA::f被 调用B::f,因此必须有一个 的定义A::f

于 2010-11-10T15:25:56.757 回答