在抽象基类中未声明抽象方法导致链接错误?

3

我有一个抽象基类Foo,其中有一个名为bar的抽象方法。

我从Foo的构造函数中调用bar。我期望子类覆盖bar(毕竟它是抽象的),然后在每个实例上调用覆盖的bar

class Foo
{
    public:
        Foo() { bar(); }
        virtual void bar() = 0;
}

然而,我遇到了一个错误:
foo.obj:-1: error: LNK2019: unresolved external symbol "protected: virtual void __cdecl Foo::bar(void)" (?bar@Foo@@MEAAXXZ) referenced in function "public: __cdecl Foo::Foo(void)" (??0Foo@@QEAA@XZ)

这种链接错误通常意味着我定义了某些东西,但没有声明它(或者反过来),这似乎又是这种情况。 如果我像这样添加bar的定义,一切都可以正常工作:
void Foo::bar() {}

这是故意出现的错误,还是链接器中的一个错误?如果是故意的那么为什么呢?我不明白为什么不能在构造函数中调用抽象方法?唯一的原因可能是基类的构造函数在子类方法定义之前被调用了,但我仍然认为我不应该得到这个错误。

我正在使用Qt Creator 2.7.0 - 基于 Qt 5.0.2 (32 bit)

2个回答

4
在构造函数中,*this 的动态类型是 Foo,并且没有定义函数 Foo::bar。不要在构造函数中调用虚函数。(除非你知道自己在做什么,否则你会知道如何提供定义。)

@MarkusMeskanen:恐怕你的问题没有意义。再仔细想想C++对象是如何工作的(特别是在存在基类的情况下的构造和析构)……正如你所声称的那样,没有一个有用的“特性”概念。 - Kerrek SB
抱歉我的知識不足...我無法實例化一個類型為Foo的對象,因此我必須有一個子類,其中已定義了bar,讓我們稱之為SubFoo。現在假設SubFoo *sf = new SubFoo;顯然是一個SubFoo對象,其中已定義了bar。因此,我不明白為什麼不能從Foo的構造函數中調用bar,因為它始終被定義。 - user2032433
@MarkusMeskanen:但是要考虑构造的顺序:首先构造所有基类子对象,然后构造所有数据成员,最后执行构造函数体。因此,在构造基类子对象时,派生对象甚至还不存在。 - Kerrek SB
@KerrebSB 哦,对了...该死。有没有一种好的方法可以在每个子类实例上调用某些东西,或者我应该只是在每个子类的构造函数中手动添加 bar(); - user2032433
不是的。我在我的任何项目中都没有与这个问题相关的代码,而且我真的不需要一个关于如何为每个实例调用某些东西的解决方案(你为什么要这样做呢?)。我只是想知道它为什么能够按照它的方式工作,并考虑是否有一个简单的解决方案可以作为补充。 - user2032433
显示剩余4条评论

2
你的问题很简单,但也很难:在派生类的构造函数之前,Foo的构造函数就已经运行了。因此,虚函数被设置为Foo设置的内容,而这个内容是未实现的。
在使用虚函数之前,必须等到构造完成,并且所有vtable条目都设置为它们最具体的版本。
虽然将构造分为三部分(首先每个构造函数更新vtable,然后是所有初始化程序,最后是所有构造函数)是可能的,但C++标准委员会已经决定反对这样做。
除了编译器建设者的简单性之外,还有其他一些优点:如果你可以访问更具体的类成员(通过调用覆盖的virtual函数),它们还没有被初始化(甚至还没有被构造),因此更具体的类的任何函数都不应该触及任何非平凡的数据成员,这比仅仅说在构造期间不应该调用虚函数更加复杂。

严谨地说,重载函数发生在编写代码时,而不是运行时...说“还没有”有点奇怪。 - Kerrek SB
@KerrekSB 你是对的;我稍微改了一下措辞,以更好地表达。 - danielschemmel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接