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

标签 c++ templates pure-virtual

在我目前正在编写的应用程序中,我创建了一个带有纯虚函数的模板类,然后另一个类继承了前者的实例并实现了虚函数。虚函数是从父类的构造函数中调用的,子类也使用它。由于链接器错误,我无法构建此代码,我也不知道为什么。这是重现我遇到的问题的简化代码版本。

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 : error LNK2019: unresolved external symbol "protected: virtual void __thiscall Foo::echo(void)" (?echo@?$Foo@H@@MAEXXZ) referenced in function "public: __thiscall Foo::Foo(int)" (??0?$Foo@H@@QAE@H@Z)

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

最佳答案

James McNellis 的回答“您不能从 echo() 的构造函数中调用 Foo<T>几乎是正确的。

您不能从 Foo<T> 虚拟调用它构造函数,因为 Foo<T> 的主体构造函数执行的对象类型为Foo<T> .还没有派生类部分。和 echo() 的虚拟电话,就像在您的代码中一样,然后转到纯虚函数:bang, dead。

但是,您可以提供纯虚函数的实现,例如 echo() ,然后以非虚拟方式调用它,例如 Foo::echo() , 来自 Foo构造函数。 :-) 除了调用 Foo执行。虽然您似乎想调用派生类的实现。

现在关于你的问题:

"I'd really like that function in the constructor."

好吧,在我写这篇文章时,您的(无效)代码如下所示:

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调用 echo 的构造函数继承自 Foo 的任何类的实现.

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

一个被称为 CRTP,即 Curiously Recurring Template Pattern,并适应您的特定问题,它可以像这样:

#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++ 类型系统中也存在一个漏洞,允许您通过使用成员指针访问 protected 基类状态而无需强制转换。前者是危险的,后者是晦涩难懂的,而且可能效率低下。所以,不要。

但希望上述解决方案之一适合您,或者进行一些合适的改编。

哦,顺便说一句,您的问题似乎是一个更普遍的问题集,被称为 DBDI初始化期间的动态绑定(bind)。您可以在 C++ 常见问题解答项 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" .

干杯,

关于c++ - 为什么尝试在模板中使用纯虚函数构建 C++ 代码时会出现链接器错误?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4210987/

相关文章:

c++ - C++ 模板函数的重载错误

c++ - 在 C++ 中是否可以定义一个纯虚函数?

c++ - 错误: "undefined reference to ` __cxa_get_exception_ptr'"when compiling Qt Creator project for Symbian

c++ - 可以将堆栈对象地址提供给 placement new 吗?

templates - Grails 2.3.8应用程序未在生产模式下从插件渲染模板

C++ 依赖于派生类的纯虚函数

c++ - 带有实现的纯虚函数

c++ - 阻止我的项目调用__CxxFrameHandler3(CRT函数)

c++ - new() 是否也为类的函数分配内存?

c++ - 具有已知类型的别名函数模板