特征中的typedef vs 类中的typedef

9

我正在查看Eigen源代码,以便进行教育目的。我注意到,在继承结构中的每个具体类模板X中,都定义了一个internal::traits<X>。在Matrix.h中可以找到一个典型示例:

namespace internal {
template<typename _Scalar, int _Rows, int _Cols, int _Options, int _MaxRows, int _MaxCols>
struct traits<Matrix<_Scalar, _Rows, _Cols, _Options, _MaxRows, _MaxCols> >
{
  typedef _Scalar Scalar;
  typedef Dense StorageKind;
  typedef DenseIndex Index;
  typedef MatrixXpr XprKind;
  enum {
    RowsAtCompileTime = _Rows,
    ColsAtCompileTime = _Cols,
    MaxRowsAtCompileTime = _MaxRows,
    MaxColsAtCompileTime = _MaxCols,
    Flags = compute_matrix_flags<_Scalar, _Rows, _Cols, _Options, _MaxRows, _MaxCols>::ret,
    CoeffReadCost = NumTraits<Scalar>::ReadCost,
    Options = _Options,
    InnerStrideAtCompileTime = 1,
    OuterStrideAtCompileTime = (Options&RowMajor) ? ColsAtCompileTime : RowsAtCompileTime
  };
};
}

现在我理解traits是一种扩展现有类的方式,您不想通过额外信息修改与某些新代码相关的类。例如,使用类模板Foo<class TAllocator>的用户可能希望利用现有的内存分配器FastAllocAlignedAlloc,但Foo需要知道如何与这两个接口,并且因此用户定义了FooTraits<AlignedAlloc>::allocate()FooTraits<FastAlloc>::allocate(),这反过来又被Foo所使用。

然而,在这种情况下,我并不容易看出只需在每个派生类中指定Scalar的问题,即在类体中使用typedef定义Matrix::Scalar。在这里使用traits类的优点是什么?它只是为了保持代码的整洁吗,即将每个类的所有相关属性存储在traits类中吗?

根据Nicol Bolas的回应进行编辑:我理解其中一些typedef可能需要保持“内部”,即不应向用户公开,这就解释了traits类。这似乎是有道理的,但是其中一些typedef,例如Scalar通过Matrix基类中的typedef可供外界使用的:

template<typename Derived> class MatrixBase
  : public DenseBase<Derived>
{
  public:

    typedef MatrixBase StorageBaseType;
    typedef typename internal::traits<Derived>::StorageKind StorageKind;
    typedef typename internal::traits<Derived>::Index Index;
    typedef typename internal::traits<Derived>::Scalar Scalar;
    typedef typename internal::packet_traits<Scalar>::type PacketScalar;
    typedef typename NumTraits<Scalar>::Real RealScalar;

这让我们回到最初的问题:为什么Scalar不只是Matrix本身的typedef?除了风格选择之外,还有其他原因吗?
2个回答

8

我怀疑,由于特性类是“internal”的,这就是使用特性类的重点。也就是说,保持这些东西“internal”。这样,在其私有接口中,即使在Matrix中也不会有很多奇怪的定义和其他内容。

考虑一下您示例中的枚举。那些“enums”(又称:C++11之前的静态constexpr变量)看起来不像用户需要知道的任何内容。这是一个实现细节,因此应该被隐藏。


MatrixBase的问题是CRTP问题。

请注意,Matrix将被定义为:

class Matrix : public MatrixBase<Matrix>

这个部分的定义会导致两件事情发生:
1. 如果“Matrix”尚未被声明为类类型,则它将成为一个合法的类,其名称可以被引用和使用。 2. 必须立即使用类型“Matrix”来实例化模板“MatrixBase”。
问题在于此时的“Matrix”是一个不完整的类。编译器尚未进入该定义的主体,因此编译器不知道它的内部结构。但是,必须立即实例化“MatrixBase”。
因此,“MatrixBase”不能使用提供的“Derived”类的任何内容。如果“Matrix”中有一些typedef,那么“MatrixBase ”就无法看到它。
现在,“MatrixBase ”的成员函数可以查看“Derived”的定义,因为这些是在完整类定义之后定义的。即使这些函数是在类作用域内定义的。
但是,你不能让“MatrixBase”的属性访问“Derived”的属性。因此需要使用traits间接方法。traits类可以使用基于不完整类型的特化来向“MatrixBase”公开定义。

我理解其中一些定义的原因。然而,Matrix继承自MatrixBase类,该类公开定义了一些这些typedefs,例如Scalar,这使我们回到了起点 - 为什么不从一开始就在Matrix中定义Scalar呢? - Moos Hueting
非常清晰 - 非常感谢您抽出时间! - Moos Hueting

4
traits 类的主要目的是避免在 CRTP 中出现递归依赖。没有它,我们会得到以下类似的结果:
template <typename T>
struct Matrix : Base<Matrix<T>> {
  typedef T Scalar;
};
template <typename Derived>
struct Base {
  typename Derived::Scalar foo();
};

在某些情况下无法编译的类。基本上,这个名为traits的类允许在不知道Matrix的声明的情况下完全声明Base<Matrix>

啊,我明白了,很有道理。谢谢! - Moos Hueting

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接