2

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

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:未解析的外部符号“受保护:虚拟 void __thiscall Foo::echo(void)”(?echo@?$Foo@H@@MAEXXZ) 在函数“public: __thiscall Foo::Foo(int )" (??0?$Foo@H@@QAE@H@Z)

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

4

2 回答 2

5

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

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

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

现在关于您的问题:

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

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

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,即奇怪重复的模板模式,并适应了您的特定问题,它可以像这样:

#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 中找到更一般的处理方法 好的,但是有没有办法模拟这种行为,就好像动态绑定在我的基类构造函数中的 this 对象上起作用一样?. 此外,对于 DBDI 是您希望派生类控制/提供基类构造的一部分的特殊情况,请参阅我的博客文章“如何使用零件工厂避免后期构造”

干杯&hth.,

于 2010-11-18T06:56:06.377 回答
4

您不能echo()Foo<T>.

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

因为echo()是纯虚类型,Foo<T>Foo<T>是对象的动态类型,所以不能echo()在构造函数中调用Foo<T>.

除非您非常熟悉对象的动态类型在构造和销毁过程中如何变化,否则最好不要尝试从构造函数和析构函数调用虚函数。

于 2010-11-18T02:07:57.560 回答