3

单一定义规则规定:
在整个程序中,一个对象或非内联函数不能有多个定义。(来自维基百科)

好吧,我知道如果在头文件中定义了一个成员函数,它就会被隐式内联,并且可以使用 ODR。

但是虚函数呢?我们知道,如果一个虚函数被多态调用,它就不能被内联。如果在头文件中定义了这样的虚函数,那会违反 ODR 吗?

例如:

//derived.hpp
#include <iostream>
class Base {
public:
  virtual ~Base() {}
  virtual void vfunc() {
    std::cout << "Base::vfunc()\n";
  }
};

class Derived : public Base {
public:
  virtual ~Derived() {}

  virtual void vfunc() {
    std::cout << "Derived::vfunc()\n";
  }
};

//foo.cpp

#include "derived.hpp"
void func() {
  Base* ptr = new Derived();
  ptr->vfunc(); //polymorphic call, can't be inlined
  delete ptr;

  ptr = new Base();
  ptr->vfunc();
  delete ptr;
}
//main.cpp

#include "derived.hpp"

int main() {
  Base* ptr = new Derived();
  ptr->vfunc(); //polymorphic call, can't be inlined
  delete ptr;

  ptr = new Base();
  ptr->vfunc();
  delete ptr;
  return 0;
}

我很好奇:

vfunc(and dtor) 在 foo.cpp 和 main.cpp 中都被多态调用(不是内联),这意味着它在整个程序中定义了两次,所以它违反了 ODR,不是吗?它是如何编译的(链接)?

我刚刚看到:

不止一个定义

在某些情况下,可以有多个类型或模板的定义。由多个头文件和源文件组成的程序通常具有多个类型的定义,但每个翻译单元不超过一个定义。如果一个程序包含多个类型的定义,那么每个定义必须是等价的(也取自维基百科)

是什么certain cases?上述情况是否属于其中之一?

4

2 回答 2

2

vfunc(and dtor) 在 foo.cpp 和 main.cpp 中被多态调用(不是内联),这意味着它在整个程序中被定义了两次,

首先:调用并不意味着定义。因此,函数调用不会告诉您是否违反了 ODR。

所以它违反了ODR,不是吗?它是如何编译的(链接)?

它可以编译,因为在类定义中定义的成员函数被隐式声明为内联,因此不会违反 ODR。这适用于两个vfunc定义以及 dtors,所以你在这里很好。

注意:声明 inline(显式或隐式)的函数与实际内联的函数之间存在差异。编译器将函数内联一次的决定可能会受到inline关键字的影响,但它曾经并且只是一个提示。如今,优化器可以比任何人更好地预测何时以及何时不内联是一个不错的选择,因此编译器可以在它认为合适的时候忽略该提示,并且它可以内联尚未声明为内联的函数。因此,对于现代编译器,inline这只是一种遵守函数 ODR 的方法,这些函数没有inline隐式声明。

更新:因此,在您的情况下,该函数被隐式声明为内联,该定义包含在两个翻译单元(两个.cpp)中,并且编译器不会内联。在这种情况下,链接器将看到函数的符号两次,但它不会因为内联声明而抱怨多个符号。

于 2013-06-17T06:49:20.660 回答
1

通过在类声明中定义函数,它一个inline声明的函数。编译器可能无法内联它(在某些情况下除外)这一事实不会改变inline声明事实。这也是事实,您的定义在每种情况下都是相同的(除非您使用宏来更改函数的内容,例如cout在两种情况下的定义方式不同,或类似的东西)。

另一方面,如果我们在头文件中有这样的内容:

class Base {
public:
  virtual ~Base() {}
  virtual void vfunc();
};

class Derived : public Base {
public:
  virtual ~Derived() {}

  virtual void vfunc();
};



void Base::vfunc()
{
  {
    std::cout << "Base::vfunc()\n";
  }
}

void Derived::vfunc()
{
  {
    std::cout << "Derived::vfunc()\n";
  }
}

现在您正在破坏 ODR,因为Derived::vfunc()它没有被声明为内联,并且通过被包含多次,它被多次定义(尽管定义完全相同)。

于 2013-06-17T06:47:58.773 回答