【问题标题】:C++ Templates, Polymorphism, and Template CovarianceC++ 模板、多态性和模板协方差
【发布时间】:2017-03-14 19:45:45
【问题描述】:

我有一个看起来像这样的“包装”模板:

template <typename T>
class Wrapper {
  virtual T* get() = 0;
}

那么我有以下继承方案:

class A {
  // things
}

class B : public A {
  // more things
}

我有另一个使用该包装器的类:

class Worker {
  void work(Wrapper<A>* wa){
    A* a = wa->get();
    //do stuff with a.
  }
}

所有可以用A* 完成的事情,都可以用B* 完成,但我不能将Wrapper&lt;B&gt;* 作为Wrapper&lt;A&gt;* 传递给Worker。有没有办法允许这样做?

我已经阅读了有关 C++ 概念的信息,但我不知道它们如何解决问题。

【问题讨论】:

标签: c++ templates inheritance wrapper covariance


【解决方案1】:

如果每个类都报告其基类:

class A {
  using Base = void;
  // things
}

class B : public A {
  using Base = A;
  // more things
}

你可以的

template<typename T>
class Wrapper : public EmptyForVoid<T::Base>::Type

并使用EmptyForVoid 进行基类选择:

template<typename T>
struct EmptyForVoid { using Type = T; };

template<>
struct EmptyForVoid<void> { struct Type {}; };

这将使Wrappers 遵循与它们包装的类型相同的继承树。

【讨论】:

  • @gd1:如果有多个碱基,这将变得更加复杂。关于shared_ptr 是首选,但我不知道是否是 OPs 用例。
  • 猜猜我们应该知道 OP 实际上在做什么。可能有一个简单的解决方案。
  • 我认为这个解决方案有各种各样的问题。这显然容易出错。它违反了关注点分离,因为B 声明了Wrapper 将如何使用它,但B 不依赖于Wrapper。而Wrapper&lt;T&gt; 不从get 返回T* 非常容易出错且令人惊讶。当您在代码中的其他地方编写一个方法时,该方法采用 Wrapper&lt;B&gt; 并期望获得 B*...
  • 看起来这就是我正在寻找的解决方案。测试它需要一些时间,但它看起来很不错。非常感谢。
【解决方案2】:

你不能直接这样做,因为 C++ 没有协方差。在默认情况下方法不是虚拟的 C++ 中(与 Java 不同,我猜是 C#),尚不清楚这是否是一个很棒的想法(并且语言已经非常复杂)。基本上你需要做的是在work 上有一个薄的模板包装器:

class Worker {
public:
  template <class T>
  void work(Wrapper<T>* wa){
    work_on_a(wa->get());
  }
private:
  void work_on_a(A* a) { // do stuff with a}
}

这仍然是安全的,因为它仅在包装类型从 A 派生或以其他方式定义到 A 的隐式转换时才会编译,否则对 work_on_a 的函数调用将失败。

您可以在这里做更复杂的事情,但有各种优点和缺点,但这会使代码尽可能简单,并最大限度地减少模板膨胀。

虽然请注意,如果work 也需要是虚拟的,这将不起作用(因为虚拟函数不能是模板),而且事情会变得很快。

另一种方法是争论包装器根本不应该传递给work;如果您需要做的只是指向A 的指针,那么这就是您应该传入的全部内容。但取决于您的真实代码,这可能不是一个选项,或者没有意义。

【讨论】:

  • 这是一个传染性模板的例子。你有一个模板类,如果你不小心,一切都会被模板化。如果可能的话,我会尝试阻止这种情况。他们对std::shared_ptr 做得很好,即使问题不一样,也许包装器足够轻巧,可以从wrapper&lt;B&gt; 转换为wrapper&lt;A&gt;
  • @gd1 确实如此,但这里模板的唯一真正缺点是它只需要是虚拟的。否则,这是非常微不足道的。如果包装器真的那么轻巧,那么它会回到我的回答中的另一点:您最有可能不接受包装器,而只是一个指向A 的指针(或更好的引用)。
【解决方案3】:

来点Curiously Recurring Template Pattern怎么样:

class SubBase {
    virtual T* get() { /* default impl. */ }
};

template <typename T>
class Base : public SubBase {};

template <typename T>
class Wrapper : public Base<T> {
    virtual T* get() = 0;
}

…我不知道你的继承图是什么样的,但只要你包装的 T 类型没有 get() 方法,这应该可以工作。


编辑:正如已经指出的那样,这个简单的 sn-p 不起作用,也不是真正的 CRTP;我只能在fairly significant modification 之后做一个这样的例子。

【讨论】:

  • 根本不是 CRTP。
  • @gd1 不会 static_cast&lt;Wrapper&lt;B&gt;*&gt;(wrapper_a_ptr) 工作,前提是 static_cast&lt;B*&gt;(a_ptr) 定义明确?
  • 在您的设计中,Wrapper&lt;B&gt;Wrapper&lt;A&gt; 不相关。我不知道那会如何工作,但也许我累了!如果可以,请在编译器中尝试。
  • 你一直在改变它,所以我的 cmets 可能会变得陈旧并且不再相关。我不知道最后一个版本,但看起来不太好。
  • @gd1 请原谅我 - 我现在正在编写一个 ideone.com 测试用例(使用当前示例,使用通用子库类型)!
猜你喜欢
  • 1970-01-01
  • 2017-08-23
  • 1970-01-01
  • 1970-01-01
  • 2021-12-31
  • 2021-02-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多