通过值传递的CRTP模式的未初始化副本

unititialized copy via CRTP pattern passed by value

本文关键字:初始化 副本 模式 CRTP 值传      更新时间:2023-10-16

我正在使用CRTP模式,并尝试定义应用于其实现的操作符。我发现未初始化的对象有一个奇怪的行为

CRTP基类:

template < class C >
struct CRTP
{
  using self_t = C;
  const self_t& self() const
  { return static_cast<const self_t&>(*this); }
  self_t& self()
  {
    const CRTP& cs = static_cast<const CRTP&>(*this);
    return const_cast<self_t&>(cs.self());
  }
  void printValue()
  { cout << "CRTP value : " << self().getValue() << endl; }
};
实现1:

struct Impl : public CRTP<Impl> {
  Impl()            = default;
  Impl(Impl&&)      = default;
  Impl(const Impl&) = default;
  explicit
  Impl(int i) : v(i) { }
  friend void swap(Impl& l, Impl& r)
  { using std::swap; swap(l.v, r.v); }
  Impl& operator=(Impl o)
  { swap(*this, o); return *this; }
  int getValue() const
  { return v; }
private:
  int v;
};
实现2:

template < class Arg >
struct Neg : public CRTP< Neg<Arg> >
{
  Neg()           = default;
  Neg(Neg&&)      = default;
  Neg(const Neg&) = default;
  explicit
  Neg(Arg arg) : a(arg) { }
  friend void swap(Neg& l, Neg& r)
  { using std::swap; swap(l.a, r.a); }
  Neg& operator=(Neg o)
  { swap(*this, o); return *this; }
  int getValue() const
  { return -a.getValue(); }
 private:
  Arg a;
};

操作符(operator!工作正常,operator~显示问题):

template < class C >
Neg<C> operator~(CRTP<C> v)
{ return Neg<C>(std::move(v.self())); }
template < class C >
Neg<C> operator-(const CRTP<C>& v)
{ return Neg<C>(v.self()); }
template < class C >
Neg<C> operator-(CRTP<C>&& v)
{ return Neg<C>(std::move(v.self())); }
现在用一个简单的main:
int main(void)
{
  auto n = -Impl(10);
  n.printValue();
  n = ~Impl(20);
  n.printValue();
}

用gcc编译,我有:

CRTP value : -10
CRTP value : 0

使用CLANG编译得到:

CRTP value : -10
CRTP value : -1186799704

现在我有两个问题:

  • 这是行为标准吗?即使我删除了默认值,它也会发生构造函数
  • 如何实现运算符中的"传递值"风格"?我有n个想要实现的运算符变量模板,我不能枚举左值和右值引用的每一个组合。

你在operator ~的参数切片你的对象。您正在按值获取CRTP<C>,这意味着CRTP子对象之外的任何内容(例如成员va)都将被删除。然后,您通过将类型为CRTP<C>的切片对象强制转换为类型为C来调用未定义行为。

按值传递时,需要按正确类型(—)的值传递对象的实际类型。对于任何C,您的对象都不是CRTP<C>类型,它们是从CRTP<C>(对于某些C)派生的类型。

如果您想保留按值传递签名,您必须接受任何内容,并使用SFINAE检查是否存在正确的基类:

template <class C>
typename std::enable_if<std::is_base_of<CRTP<C>, C>::value, Neg<C>>::type operator~ (C v)
{ return Neg<C>(std::move(v.self())); }

或者你可以使用完全转发,用同样的技巧