使用奇怪的重复模板模式 (CRTP) 在抽象基类中实现赋值运算符

Posted

技术标签:

【中文标题】使用奇怪的重复模板模式 (CRTP) 在抽象基类中实现赋值运算符【英文标题】:Implementing the assignment operator in an abstract base class using the curiously recurring template pattern (CRTP) 【发布时间】:2021-11-30 21:39:46 【问题描述】:

我正在为静态/动态数组编写一个基于 CRTP 的抽象基类。我打算在基类中放置尽可能多的方法,以便派生类中没有代码重复。我已经让索引运算符工作了,但我正在努力使用赋值 (=) 运算符。

/** Array base class. */
template <class t_derived_class, class data_type>
class ArrayBase

  private:
  t_derived_class& Derived;

  public:
  /** Default constructor. */
  ArrayBase() : Derived(static_cast<t_derived_class&>(*this)) 

  /** Pure virtual desctructor. */
  virtual ~ArrayBase() ;

  /** Index operator overloads. */
  data_type& operator[](const std::size_t _index)
  
    return *(Derived.begin() + _index);
  

  const data_type& operator[](const std::size_t _index) const
  
    return *(Derived.begin() + _index);
  

  /** Assignment operator overloads. */
  t_derived_class& operator=(const t_derived_class& _other_derived)
  
    for(std::size_t i = 0; i < Derived.size(); ++i) Derived[i] = _other_derived[i];
    return Derived;
  
;
/** Static array class. */
template <class data_type, int array_size>
class StaticArray : public std::array<data_type, array_size>, public ArrayBase<StaticArray<data_type, array_size>, data_type>

  using Base = ArrayBase<StaticArray<data_type, array_size>, data_type>;
  friend Base;

  public:
  /** Default constructor. */
  StaticArray() : std::array<data_type, array_size>() 

  /** Default destructor. */
  ~StaticArray() = default;

  using Base::operator[];
  using Base::operator=;
;

/** Dynamic array class. */
template <class data_type>
class DynamicArray : public std::vector<data_type>, public ArrayBase<DynamicArray<data_type>, data_type>

  // ...
;
int main()

  StaticArray<double, 3> array1;
  array1[0] = 1.0;
  array1[1] = 2.0;
  array1[2] = 3.0;

  // This code compiles
  StaticArray<double, 3> array2 = array1;

  // This code does not compile
  array2 = array1; 

  return 0;

当我使用上述赋值运算符时,我的 IDE (CLion) 出现以下错误:

Object of type 'StaticArray<Apeiron::Float, 3>' (aka 'StaticArray<double, 3>') cannot be assigned because its copy assignment operator is implicitly deleted

编译器错误是:

error: ‘Apeiron::StaticArray<double, 3>& Apeiron::StaticArray<double, 3>::operator=(const Apeiron::StaticArray<double, 3>&)’ cannot be overloaded with ‘t_derived_class& Apeiron::ArrayBase<t_derived_class, data_type>::operator=(const t_derived_class&) [with t_derived_class = Apeiron::StaticArray<double, 3>; data_type = double]’
   90 | class StaticArray : public std::array<data_type, array_size>, public ArrayBase<StaticArray<data_type, array_size>, data_type>
      |       ^~~~~~~~~~~
Array.h:62:20: note: previous declaration ‘t_derived_class& Apeiron::ArrayBase<t_derived_class, data_type>::operator=(const t_derived_class&) [with t_derived_class = Apeiron::StaticArray<double, 3>; data_type = double]’
   62 |   t_derived_class& operator=(const t_derived_class& _other_derived)

谁能告诉我如何让它工作?

【问题讨论】:

请注意,如果DynamicArray 大小不同,您的作业会表现得很糟糕。 @molbdnilo 你能详细说明一下吗?我没有包含我在两个派生类中实现的大小检查方法,但这不是唯一的问题吗?然后当然检查静态数组的大小是否相同。 你需要定义operator=吗,默认的(在Derived中)不可以吗? @Jarod42 事实上,我真正想要定义的赋值运算符是将data_type 类型的单个值分配给所有数组条目。我只是碰巧从复制赋值运算符开始。但我同意,我应该可以在std::arraystd::vector 中使用预定义的复制赋值运算符。 【参考方案1】:

由于您的ArrayBase 的成员变量是引用,因此隐式声明的ArrayBase::operator= 将是automatically deleted。

另一种方法是去掉成员变量,直接使用帮助函数获取派生类的reference

template <class t_derived_class, class data_type>
class ArrayBase

  private:
  t_derived_class& Derived() noexcept 
    return static_cast<t_derived_class&>(*this);
  ;

  const t_derived_class& Derived() const noexcept 
    return static_cast<const t_derived_class&>(*this);
  ;

  public:
  /** Pure virtual desctructor. */
  virtual ~ArrayBase() ;

  /** Index operator overloads. */
  data_type& operator[](const std::size_t _index)
  
    return *(Derived().begin() + _index);
  

  // ...
;

【讨论】:

gcc 仍然拒绝代码 Demo 并出现错误:"'constexpr StaticArray& StaticArray::operator=(const StaticArray &)' 不能用 't_derived_class& ArrayBase::operator=(const t_derived_class&) [with t_derived_class= StaticArray; data_type = float]'" 重载。由于其他编译器使用错字return Derived; 而不是return Derived(); 通过,我怀疑它并没有像预期的那样糟糕。 派生类应该删除using Base::operator=;。 Demo. 但正如我所说,错字return Derived; 没有被发现,因此该方法没有被实例化,并且可以被删除......(碰巧默认Derived::operator= 已经完成了这项工作)。 你说得对,这里没有实例化用户自定义的operator=

以上是关于使用奇怪的重复模板模式 (CRTP) 在抽象基类中实现赋值运算符的主要内容,如果未能解决你的问题,请参考以下文章

什么是奇怪的重复模板模式(CRTP)?

CRTP 特征仅适用于模板派生类

CRTP:根据派生类内容启用基类中的方法

c++派生类的类型列表

模板模式

浅谈 CRTP:奇异递归模板模式