【问题标题】:Reference becomes dangling in expression template引用在表达式模板中变得悬空
【发布时间】:2021-02-08 15:04:35
【问题描述】:

我写了一个表达式模板代码,但出现了错误。
以下只是一个最小的可重现示例。

#include <iostream>

template<typename Derived>
class VecExp {
 public:
  double operator[](int i) const {return static_cast<Derived const&>(*this)[i];}
};

template<int n>
class Vector : public VecExp<Vector<n>> {
 public:

  template<int m>
  Vector(const double(&arr)[m]) {
    static_assert(m==n, "");
    for(int i=0; i<m; ++i) data[i] = arr[i];
  }

  template<typename E>
  Vector(const VecExp<E>& exp) {
    for(int i=0; i<n; ++i) data[i] = exp[i];
  }

  double operator[](int i) const {std::cout<<this<<std::endl; return data[i];}
  double& operator[](int i) {return data[i];}
  double data[n];
};

template<typename E1, typename E2>
class VectorSum : public VecExp<VectorSum<E1, E2>>{
  E1 const& lhs;
  E2 const& rhs;
 public:

  VectorSum(E1 const& lhs, E2 const& rhs) : lhs(lhs), rhs(rhs) {
//    std::cout << &(this->lhs) << ", " << &(this->rhs) << std::endl;
  }

  double operator[](int i) const {return lhs[i] + rhs[i];}
};

template<typename E1, typename E2>
VectorSum<E1, E2>
operator + (VecExp<E1> const& lhs, VecExp<E2> const& rhs) {
  return VectorSum<E1, E2>(*static_cast<const E1*>(&lhs), *static_cast<const E2*>(&rhs));
}

int main() {
  Vector<16> v({1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16});
  std::cout << &v << std::endl;
  const auto sum = v + v + v + v;
  Vector<16> v2 = sum; // error at here

  std::cout << sum[0] << std::endl;

  return 0;
}

当我尝试使用添加超过 3 个Vectors 的VectorSum&lt;...&gt;(sum in main())时,在发布版本中会出现以下错误。

Exception 0xc0000005 encountered at address 0x7ff76f8a1189: Access violation reading location 0x00000000

所以我像上面一样连接了指针打印机,然后它说

00000067C1EFF920
0000000000000000

Process finished with exit code -1073741819 (0xC0000005)

如果我附加更多打印机(即:在operator + 或VecSum ctor,...),那么突然一切正常。
谁能告诉我为什么会发生这个错误?

在 Windows 上使用 CLion,安装了 Visual Studio community 2019。

【问题讨论】:

    标签: c++ templates reference


    【解决方案1】:

    v + v 是VectorSum&lt;Vector&lt;16&gt;, Vector&lt;16&gt;&gt; 类型的表达式。

    v + v + v 是VectorSum&lt;VectorSum&lt;Vector&lt;16&gt;, Vector&lt;16&gt;&gt;, Vector&lt;16&gt;&gt; 类型的表达式,其中第一个模板参数的类型很重要:

       VectorSum<VectorSum<Vector<16>, Vector<16>>, Vector<16>>
    //           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ (B)
    // ^^^^^^^^^ (A)
    

    与此特化关联的对象A 存储B const&amp; 类型的常量引用,但是一旦表达式v + v + v 到期,子表达式v + v 的生命周期也会到期,这意味着您正在存储一个表达式对象(A)中的悬空引用(作为数据成员lhs)。

    仅存储表达式v + v 的表达式模板时看不到此问题的原因是,关联的特化对象的lhs 和rhs 数据成员都引用了其生命周期的对象v延伸到main() 的末尾。

    最后,在使用诸如auto 等占位符类型将表达式模板存储在中间变量中时要小心,因为它会 1) 可能会产生诸如上述问题的生命周期问题,并且 2) 可能会出乎意料普通读者,正如人们所期望的那样(与std::vector&lt;bool&gt;::operator[] 比较))auto = v + v 会导致decltype(v)(即Vector&lt;16&gt;),而不是应该对客户端隐藏的代理类型。

    【讨论】:

    • 在搜索了如何实现之后,absl库给了我答案。它检查E1 和E2 的类型并决定lhs 和rhs 类型是否被引用。感谢您的回答!
    • @김선달 absl/Titus Winters 在设计库 API 时通常会有一两个很好的建议:s!乐于助人。
    猜你喜欢
    • 2015-11-08
    • 1970-01-01
    • 1970-01-01
    • 2011-11-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多