【问题标题】:Template class + virtual function = must implement?模板类+虚函数=必须实现?
【发布时间】:2010-12-02 03:55:02
【问题描述】:

这段代码:

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&lt;int&gt;Primitive&lt;char&gt;... 但是Primitive&lt;float&gt; 怎么样?这似乎是个问题,因为浮点数没有 %= 运算符。但实际上并非如此,因为我永远不会在Primitive&lt;float&gt; 上致电operator %=。 这是模板的深思熟虑的功能之一。

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

【问题讨论】:

  • 如果你从不调用它,那么拥有这个函数有什么意义呢?如果它在您的类型 T 实例上调用 DoSomething,则类型 T 必须实现 DoSomething 方法。
  • 重点是,如果合适的话,我可能会调用 DoSomething,也可能不会,这取决于上下文。 Ofcouse,如果我真的调用它,那么 T 必须实现任何必要的。这是模板更有用的属性之一。
  • 这闻起来是糟糕的设计。 :|你到底想做什么?不是您的详细目标,而是您的任务目标。
  • 您可以通过要求您的客户定义一个免费的 SomeFunction(T&); 来轻松解决它。您刚刚使用 SomeFunction(t); 调用的函数,该调用将选择任何可用的函数(作为后备,您自己的函数)。
  • @litb:如果我想使用原始类型的模板怎么办?我需要编译器在 vtable 中填写一个虚拟 thunk。

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


【解决方案1】:

将虚拟对象放入可选择的基类...

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 与作为 Methods 模板参数提供的 Jumper、Crawler 或 JumperCrawler 一起使用;意识到您需要从它派生,以便您可以在子类中实现 Jumping 和/或 Crawling。

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

【讨论】:

  • 再看问题。提供的代码工作正常。问题是,如果我将 ICanBoostJumpingAndCrawling::Jump 定义为 VIRTUAL,那么模板机制将要求我定义 T::Jump() (这与提升无关)。这就是我要解决的问题。
【解决方案2】:

这不是错误,而是一项功能——说真的。有一次,大多数编译器不会编译代码,这正是您给出的原因。他们已经被更新来编译它,部分原因是标准需要它。

C++ 0x 标准中有一个名为“概念”的功能已经有一段时间了,它允许您指定 T 需要一个名为“SomeFunction”的成员,包括它的返回类型、参数类型等。

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

虽然它没有那么好,但 Boost 确实有一个 Concept Checking 库,可以满足您的要求。

【讨论】:

  • 我认为您误解了这个问题。如果函数从未被调用,他希望能够不声明 DoSomething。
  • @shoosh,如果我理解正确的话,他想一直声明它,但如果T 没有这个功能,他不想收到错误消息
  • 我不确定你有没有这个问题。问题是代码不会编译,而不是它会。整个想法是 T 可能有一个 SomeFunction 成员,但可能没有。
  • 是的,你是对的——我没有仔细阅读他的问题。
【解决方案3】:

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

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

};

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

【讨论】:

  • 不是一个选项:P 如果我有无限大的类集支持 SomeFunction,而无限大的类集不支持呢?
  • 那么你的可执行文件大小将是无限的并且不是很有用。
【解决方案4】:

所以编译器应该能够在编译单元内计算出正在使用的内容。一旦您开始涉及多个编译单元,它就不再具有有限的范围,并采取必要的步骤来确保所有类都可以编译。

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

要解决 virtual 的问题,最好的办法是将问题推迟到其他类 - 不要将 virtual 放在模板中。

也许

  • 在您的模板中添加“特征”部分可以节省成本。
  • 使用多重继承来定义复合模板,例如使用 shims

即。

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>;

我真的会回去问问你是否为制作模板提取了正确级别的信息。

【讨论】:

    猜你喜欢
    • 2011-12-22
    • 2017-01-20
    • 2016-10-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-04
    • 1970-01-01
    • 2012-08-14
    相关资源
    最近更新 更多