【问题标题】:C++ base class constructor taking derived class as argument (?)以派生类为参数的 C++ 基类构造函数(?)
【发布时间】:2021-02-14 10:26:09
【问题描述】:

用例:

  • Vector 类(实现一些数学运算)和派生的 Vector2D
  • 理想情况下,两个类都应允许彼此“复制构造”

Vector

namespace mu {
template<std::size_t N, typename T>
class Vector {
  public:
  // ...

  template <typename... TArgs>
  Vector(TArgs... args) : data({args...}) {}

  Vector(const Vector &other) = default; // copy constructor

  // ...
  protected:
    std::array<T, N> data;
};
}

Vector2D

namespace mu {
template<typename T>
class Vector2D : public Vector<2,T> {

  public:

  using Vector<2, T>::Vector; // inherit base class constructors

  Vector2D(const Vector<2, T>& other) : Vector<2, T>(other) {}

  // Vector2D specific functions, e.g. rotation
  //...

};
}

注意:实际的类包含更多内容,但我将其浓缩为我认为在这里最重要的代码。

问题是我无法实现一种可以从Vector2D 构造Vector 的方法,请参见下面的代码。所有其他情况都可以正常工作。

// Example 1 (compiles)
mu::Vector<2, int> a{1, 2};
mu::Vector<2, int> b{a};

// Example 2 (compiles)
mu::Vector2D<int> c{1, 2};
mu::Vector2D<int> d{c};

// Example 3 (compiles)
mu::Vector<2, int> e{1, 2};
mu::Vector2D<int> f{e};

// Example 4 (doesn't compile)  <-- how to get this to work?
mu::Vector2D<int> g{1, 2};
mu::Vector<2, int> h{g};

当然,更普遍的问题是继承是否是构建这些类的正确方法。但我希望Vector2D 拥有Vector 的所有功能以及Vector 没有的其他功能。

【问题讨论】:

  • 此数据不应组织为类层次结构。拥有一个单独的Vector2D 类并没有多大意义。 2D 向量是 Vector&lt;2, ..&gt; 仅此而已。所以它是一个简单的别名,而不是一个全新的类。或者,Vector2D 使用 Vector 作为其 内部 表示形式(因此包含,而不是继承)。
  • 我也想过这个问题,但是 Vector2D 不仅是 Vector 的“typedef”,而且具有 Vector 所没有的更具体的功能。例如,它包含旋转 Vector2D 的函数。
  • 您可以旋转任意维度的向量,但这不是重点。您不是第一个尝试以这种方式实现几何向量的人。去过也做过。它不起作用。
  • 它不起作用,因为除了这个小的初始化问题之外还有很多问题。您可能会或可能不会在您的特定应用程序中遇到它们,但您应该知道一般公共继承适用于基于身份的对象,但效果不佳基于值的对象。
  • 我建议宁愿将所有这些特定 tp 2D 向量的附加函数实现为独立的非成员函数,而忘记单独的类。

标签: c++ c++11 templates inheritance constructor


【解决方案1】:

您的Vector 类有两个构造函数:一个模板(用于值)和默认的复制构造函数。

问题:复制构造函数是首选,但前提是存在完全匹配。

所以,用a初始化b

mu::Vector<2, int> a{1, 2};
mu::Vector<2, int> b{a};

首选复制构造函数,因为a 是完全匹配

但是,用g 初始化h

mu::Vector2D<int> g{1, 2};
mu::Vector<2, int> h{g};

g 可以转换为mu::Vector&lt;2, int&gt;不是完全匹配,因此首选模板构造函数,但模板构造函数不兼容。

一个可能的解决方案:当只有一个参数并且参数派生自mu::Vector时,SFINAE禁用模板构造函数。

例如

template <typename... TArgs,
          typename std::enable_if_t<sizeof...(TArgs) == N
                                or (not std::is_base_of_v<Vector, TArgs> && ...), int> = 0>
Vector(TArgs const & ... args) : data({args...}) {}

【讨论】:

  • != 1?为什么?我们在那里有N
  • @n.'pronouns'm。 - 好吧...也许没用...但是这会在有两个或多个参数的情况下启用构造函数,所有参数都派生自Vector
  • 你可以在那儿直接说== N
  • @n.'pronouns'm。 - 但是使用== N 你禁止这样做:mu::Vector&lt;5, int&gt; a{1, 2, 3};,其中内部std::array&lt;int, 5&gt; 被初始化{1, 2, 3, 0, 0},因此对于未表达的值有额外的零。鉴于原始代码允许这样做,我更愿意保持这种可能性。
  • 我宁愿不允许这样做,因为在现实世界中没有这样的东西有用的场景,但当然这只是我的意见。
【解决方案2】:

撇开这个特定任务的继承适用性不谈,失败的直接原因是包罗万象的template &lt;typename... TArgs&gt;构造函数。它会拦截所有不是副本的构造——从派生类对象构造的构造不是副本。这是因为从 Derived 到 Base 的转换是一种转换,而模板构造函数不需要转换,所以更匹配。

如果参数适合构造 std::array 成员,您希望将 catch-all 构造函数限制为仅包含在重载决议中。这是 SFINAE 的标准应用程序。

【讨论】:

    猜你喜欢
    • 2015-08-18
    • 2016-07-19
    • 2021-08-29
    • 1970-01-01
    • 1970-01-01
    • 2018-07-16
    • 2020-11-28
    • 1970-01-01
    • 2018-07-21
    相关资源
    最近更新 更多