模板类+虚函数=必须实现?

时间:2009-09-28 22:49:14

标签: c++ virtual-functions class-template

此代码:

template <typename T>
struct A
{
     T t;

     void DoSomething()
     {
          t.SomeFunction();
     }
};

struct B
{
};

A<B> a;
只要我从不打电话给a.DoSomething()

很容易编译而没有任何投诉。

但是,如果我将DoSomething定义为虚函数,我将收到编译错误,指出B未声明SomeFunction。我可以在某种程度上看到它为什么会发生(DoSomething现在应该在vtable中有一个条目),但我不禁觉得它并没有真正的义务。加上它很糟糕。

有没有办法克服这个问题?

编辑2:好的。我希望这次它能够做到: 假设我正在进行侵入式引用计数,因此所有实体都必须从基类Object继承。我怎样才能成为原始类型?我可以定义:

template <typename T>
class Primitive : public Object
{
    T value;
public:
    Primitive(const T &value=T());

    operator T() const;

    Primitive<T> &operator =(const T &value);
    Primitive<T> &operator +=(const T &value);
    Primitive<T> &operator %=(const T &value);

    // And so on...
};

所以我可以使用Primitive<int>Primitive<char> ... 但Primitive<float>怎么样?这似乎是一个问题,因为浮点数没有%=运算符。但事实上,事实并非如此,因为我永远不会在operator %=上致电Primitive<float>。 这是模板的深思熟虑的功能之一。

如果出于某种原因,我会将operator %=定义为虚拟。或者,如果我要从dll预导出Primitive<float>以避免链接错误,即使我从未在operator %=上调用Primitive<float>,编译器也会抱怨。如果它只是在operator %=的vtable中填充Primitive<float>的虚拟值(引发异常?),那么一切都会好的。

4 个答案:

答案 0 :(得分:2)

将虚拟内容放入可选择的基类......

struct Jumper
{
    virtual void Jump =0;
};

struct Crawler
{
    virtual void Crawl() =0;
};

struct JumperCrawler:
    public Jumper,
    public Crawler
{
};

template<typename T, typename Methods>
class ICanBoostJumpingAndCrawling :
    public Methods
{
    T t;
};

现在,您可以将ICanBoostJumpingAndCrawling与Jumper,Crawler或JumperCrawler一起用作Methods模板参数;意识到你需要从它派生,以便你可以在子类中实现跳跃和/或爬行。

仅供参考,这使得名称“ICanBoostJumpingAndCrawling”完全具有误导性,因为它可能会也可能不会这样做;这意味着它应该被重命名为“Booster”。

答案 1 :(得分:0)

这不是一个错误,它是一个功能 - 认真。有一次,大多数编译器都不会编译代码,正是因为你提供的原因。它们已被更新以编译它,部分原因是标准需要它。

很长一段时间,在C ++ 0x标准中有一个名为“Concepts”的功能,它允许你指定T需要一个名为'SomeFunction'的成员,包括它的返回类型,参数类型等。

可悲的是,在标准委员会的最后一次会议上,他们决定完成概念会延迟大多数人想要等待的标准,所以他们删除了它们。

虽然它不是那么好,但Boost确实有一个Concept Checking库可以做你想要的。

答案 2 :(得分:0)

解决此问题的一种方法是将A专用于模板参数B而不是声明DoSomething()

template <>
struct A<struct B>
{
     T t;

};

当然,这意味着您现在必须从头开始实现整个A结构。

答案 3 :(得分:0)

因此编译器应该能够在编译单元中解决正在使用的问题。一旦你开始涉及多个编译单元,它就不再具有有限的范围,并采取必要的步骤来确保所有类都可以编译。

对于从库导出不强制预导出,只要使用相同的编译器编译所有代码,就可以忽略有关不导出模板的警告,模板将在所有位置编译相同的模板,仅编译每个编译单元中的必要内容。

要解决虚拟问题,那么您可以做的最好的事情是将问题推迟到其他类 - 不要将虚拟放在模板中。

也许

  • 在您的模板中添加“特征”部分可以提供便宜的出路。
  • 使用多重继承来定义复合模板,例如使用填充程序

template <typename T>
class Additive
{
public:
    Primitive<T> &operator =(const T &value);
    Primitive<T> &operator +=(const T &value);
};

template <typename T>
class Multiplicative
{
public:
    Primitive<T> &operator *=(const T &value);
    Primitive<T> &operator /=(const T &value);
};

template <typename T>
class Integers : public Additive<T>, public Multiplicative<T>;

我真的回过头来询问你是否抽出了制作模板的正确信息。