【问题标题】:Linker error on pure virtual function call with gcc使用 gcc 进行纯虚函数调用时的链接器错误
【发布时间】:2012-07-12 03:44:12
【问题描述】:

我和一个朋友就对象的构造进行了一次非常有趣的讨论,最终得到了这段代码:

#include <iostream>

class Parent {
  public:
    Parent( ) {
      this->doSomething( );
    }

    virtual void doSomething( ) = 0;
};

class Child : public Parent {
    int param;

  public:
    Child( ) {
      param = 1000;
    }

    virtual void doSomething( ) {
      std::cout << "doSomething( " << param << " )" << std::endl;
    }
};

int main( void ) {
    Child c;
    return 0;
}

我知道标准没有定义从构造函数或析构函数调用纯虚函数时的行为,这也不是我如何在生产中编写代码的实际示例,它只是检查什么编译器会。

在 Java 打印中测试相同的构造

做某事(0)

这是有道理的,因为 param 在从父构造函数调用 doSomething() 时并未初始化。

我希望在 C++ 中有类似的行为,不同之处在于 param 在调用函数时包含任何内容。

编译上述代码会导致链接器错误,(c++ (Ubuntu 4.4.3-4ubuntu5.1) 4.4.3) 表示对 Parent::doSomething( ) 的引用未定义。

所以,我的问题是:为什么这是一个链接器错误?如果这是一个错误,我希望编译器会抱怨,特别是因为有一个函数的实现。 对链接器在这种情况下如何工作的任何见解或对进一步阅读的参考将不胜感激。

提前感谢您!我希望这个问题不是重复的,但我找不到类似的问题..

【问题讨论】:

  • 你没有在基类中实现函数,你只是尝试调用它。

标签: c++ linker


【解决方案1】:

所以,我的问题是:为什么这是一个链接器错误?如果这是一个错误,我希望编译器会抱怨,特别是因为有该函数的实现。对链接器在这种情况下如何工作的任何见解或对进一步阅读的参考将不胜感激。

让我们进一步扩展一下。

为什么是链接器错误?

因为编译器从构造函数中注入了对Parent::doSomething()的调用,但是链接器没有找到函数的定义。

我希望编译器会抱怨,特别是因为有一个函数的实现。

这是不正确的。该函数有一个覆盖,可以通过虚拟调度访问,但函数Parent::doSomething() 没有定义。那里有一个微妙但重要的区别,可以通过禁用动态调度以不同的方式进行测试。您可以通过使用类名限定函数来禁用特定调用的动态调度,例如,在Child::doSomething() 中,如果添加Parent::doSomething(),这将生成对Parent::doSomething() 的调用,而不使用动态调度来调用最终覆盖器.

为什么这很重要?

这很重要,因为即使函数是 pure-virtual(纯虚拟意味着动态调度永远不会调度到那个特定的重载),它也可以被 定义 并且

struct base {
   virtual void f() = 0;
};
inline void base::f() { std::cout << "base\n"; }
struct derived : base {
   virtual void f() {
      base::f();
      std::cout << "derived\n";
   }
};
int main() {
   derived d;
   d.f();        // outputs: base derived
}

现在,C++ 有一个单独的编译模型,这意味着函数不需要在这个特定的翻译单元中定义。也就是说,Parent::doSomething() 可以在链接到同一个程序的不同翻译单元中定义。编译器不可能知道是否有任何其他 TU 会定义该函数,只有链接器知道,因此抱怨的是链接器。

任何关于链接器在这种情况下如何工作的见解或对进一步阅读的参考将不胜感激。

如前所述,编译器(此特定编译器)在Parent 级别添加对特定覆盖的调用。与所有其他函数调用一样,链接器试图找到在任何翻译单元中定义的符号并且失败,从而触发错误。

pure-virtual 说明符的唯一目的是在创建虚拟表时避免函数的隐式使用(标准中的odr-use)。也就是说,pure 说明符的唯一目的不是将对该符号的依赖添加到虚拟表中。这反过来意味着链接器将不需要在程序中存在符号 (Parent::doSomething) 以用于动态调度 (vtable),但如果程序显式使用它,它仍然需要该符号。

【讨论】:

  • 是的,你是对的(这就是我的意思,用词不当)。这实际上可以通过定义函数来测试。
  • 感谢您的详细解答。
【解决方案2】:

编译器知道,当您从构造函数内部调用doSomething 时,该调用必须引用此类中的doSomething,即使它是虚拟的。所以它会优化掉虚拟调度,而只是做一个普通的函数调用。由于函数没有在任何地方定义,这会导致链接时出错。

【讨论】:

    【解决方案3】:

    在调用doSomething()的指针处,编译器可以确定*this的动态类型是Parent,所以不需要间接调用函数。这种行为很大程度上取决于您使用的编译器/链接器。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-01-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-05-14
      • 2011-08-06
      相关资源
      最近更新 更多