首页 文章

为什么在模板中使用纯虚函数尝试构建C代码时会出现链接器错误?

提问于
浏览
1

在我正在编写的应用程序中,我创建了一个带有纯虚函数的模板类,然后是另一个继承前者实例并实现虚函数的类 . 从父的构造函数调用虚函数,子构造函数也使用该构造函数 . 由于链接器错误,我无法构建此代码,我无法弄清楚原因 . 这是重现我遇到的问题的简化版代码 .

template <typename T> class Foo
{
public:
    Foo(T a)
    {
        x = a;
        echo();
    }

protected:
    T x;
    virtual void echo() = 0;
};

class Bar : public Foo<int>
{
public:
    Bar(int a) : Foo<int>(a)
    {
    }

    void echo();
};

void Bar::echo()
{
    cout << "value: " << x << endl;
}

int main(int argc, char* argv[])
{
    Bar bar(100);
    return 0;
}

链接器错误在MSVC中显示如下:

purevirttest.obj:错误LNK2019:未解析的外部符号“protected:virtual void __thiscall Foo :: echo(void)”(?echo @?$ Foo @ H @@ MAEXXZ)在函数“public:__thiscall Foo :: Foo”中引用( int)“(?? 0?$ Foo @ H @@ QAE @ H @ Z)

如果我将调用从Foo的构造函数移动到echo(),代码构建并执行得很好,我可以毫无问题地调用bar.echo() . 问题是我真的很喜欢构造函数中的那个函数 . 对这个谜团的任何解释都非常赞赏 .

2 回答

  • 4

    James McNellis ' answer that "You can'从 Foo<T> 的构造函数中调用 echo() “几乎是正确的 .

    您无法从 Foo<T> 构造函数中虚拟调用它,因为 Foo<T> 构造函数的主体执行该对象时类型为 Foo<T> . 还没有派生类的部分 . 并且在您的代码中, echo() 的虚拟调用将转到纯虚函数:bang,dead .

    但是,您可以提供纯虚函数的实现,如 echo() ,然后从 Foo 构造函数中非_虚拟地调用它,如 Foo::echo() . :-)除了调用 Foo 实现 . 虽然看起来你实现了'd like to call the derived class' .

    现在关于你的问题:

    “我真的很喜欢构造函数中的那个函数 . ”

    好吧,正如我写的那样,你的(无效)代码如下所示:

    template <typename T> class Foo
    {
    public:
        Foo(T a)
        {
            x = a;
            echo();
        }
    
    protected:
        T x;
        virtual void echo() = 0;
    };
    
    class Bar : public Foo<int>
    {
    public:
        Bar(int a) : Foo<int>(a)
        {
        }
    
        void echo();
    };
    
    void Bar::echo()
    {
        cout << "value: " << x << endl;
    }
    
    int main(int argc, char* argv[])
    {
        Bar bar(100);
        return 0;
    }
    

    据我了解你的问题描述,你希望 Foo 构造函数调用从 Foo 继承的任何类的 echo 实现 .

    有很多方法可以做到这一点;它们都是将派生类实现的知识带到基类 .

    一个被称为 CRTP ,奇怪的重复模板模式,并适应您的特定问题,它可以像这样:

    #include <iostream>
    
    template< class XType, class Derived >
    class Foo
    {
    public:
        Foo( XType const& a )
            : state_( a )
        {
            Derived::echo( state_ );
        }
    
    protected:
        struct State
        {
            XType   x_;
            State( XType const& x ): x_( x ) {}
        };
    
    private:
        State   state_;
    };
    
    class Bar
        : public Foo< int, Bar >
    {
    private:
        typedef Foo< int, Bar >     Base;
    public:
        Bar( int a ): Base( a ) {}
        static void echo( Base::State const& );
    };
    
    void Bar::echo( Base::State const& fooState )
    {
        using namespace std;
        cout << "value: " << fooState.x_ << endl;
    }
    
    int main()
    {
        Bar bar(100);
    }
    

    以上是一个不错的解决方案,但也不好 . 如果您的实际问题是从基类构造函数调用派生类非静态成员函数,那么唯一的“好”答案是Java或C#,它允许您执行此类操作 . 它在C中故意不受支持,因为它很容易在无意中尝试访问派生类对象中尚未初始化的东西 .

    无论如何,几乎总是存在某些编译时解决方案的地方,还有一个运行时解决方案 .

    您可以简单地将要执行的函数作为构造函数参数传递,如下所示:

    #include <iostream>
    
    template< class XType >
    class Foo
    {
    protected:
        struct State
        {
            XType   x_;
            State( XType const& x ): x_( x ) {}
        };
    
    public:
        Foo( XType const& a, void (*echo)( State const& ) )
            : state_( a )
        {
            echo( state_ );
        }
    
    private:
        State   state_;
    };
    
    class Bar
        : public Foo< int >
    {
    private:
        typedef Foo< int >  Base;
    public:
        Bar( int a ): Base( a, echo ) {}
        static void echo( Base::State const& );
    };
    
    void Bar::echo( Base::State const& fooState )
    {
        using namespace std;
        cout << "value: " << fooState.x_ << endl;
    }
    
    int main()
    {
        Bar bar(100);
    }
    

    如果你研究这两个程序,你可能会注意到一个微妙的区别(除了编译时与运行时知识转移) .

    最后,有一些涉及脏转换的解决方案,在C类型系统中也存在一个漏洞,它允许您通过使用成员指针访问受保护的基类状态而不进行转换 . 前者是危险的,后者是模糊的,可能效率低下 . 所以,不要 .

    但希望上面的解决方案之一适合您,或适当的适应 .

    哦,顺便说一句,你的问题似乎是一个更为一般的问题,被称为 DBDI ,初始化过程中的动态绑定 . 您可以在C FAQ项目23.6 Okay, but is there a way to simulate that behavior as if dynamic binding worked on the this object within my base class's constructor?中找到更一般的处理方法 . 此外,对于DBDI的特殊情况,您希望由派生类控制/提供基类构造的一部分,请参阅我的博客条目"How to avoid post-construction by using Parts Factories" .

    干杯&hth . ,

  • 4

    你不能从 Foo<T> 的构造函数中调用 echo() .

    Foo<T> 的构造函数内部,对象的动态类型是 Foo<T> . 直到 Foo<T> 构造函数完成后,动态类型才变为 Bar .

    由于 echo()Foo<T> 中是纯虚拟的,并且由于 Foo<T> 是对象的动态类型,因此无法在 Foo<T> 的构造函数中调用 echo() .

    除非您非常熟悉对象的动态类型在构造和破坏期间如何变化,否则这将是一个好主意不要尝试从构造函数和析构函数中调用虚函数 .

相关问题