2012-03-02 84 views
9

我明白,做一些类似如下:防止表达式模板绑定到右值引用

auto&& x = Matrix1() + Matrix2() + Matrix3(); 
std::cout << x(2,3) << std::endl; 

会导致无声运行时错误,如果矩阵运算使用表达式模板(如boost::ublas)。

是否有任何设计表达式模板的方法来防止编译器编译可能导致在运行时使用过期临时对象的代码?

(我已经尝试失败来解决这个问题,尝试here

+3

如果你禁止这种绑定,'operator +(expression_template const&,expression_template const&)'也不会编译。 – 2012-03-02 04:10:10

+0

@ R.MartinhoFernandes:为什么'operator +'必须通过'expression_template const&'来接受它的参数?我可以想象,'operator +'可以通过某种代理来接受它的参数,它仍然不允许'const引用'被不安全地绑定到表达式模板。 (我不是说这是可能的,但至少不是不可能的)。 – Mankarse 2012-03-02 04:47:03

+0

@Mankarse您不能混合隐式转换和模板类型扣除。由于必须为'operator +'选择类型推导才能工作,因此参数必须是表达式模板的类型。 (除非我误解你的意思是“某种代理人”) – 2012-03-02 04:54:31

回答

7

是否有设计表达式模板,以防止编译器编译这样的代码,可能导致在使用的任何方式在运行时过期临时工?

不,这实际上是在C++ 11的最终标准化之前得到认可的,但我不知道它是否曾经被提交给委员会的通知。不是一个修复会很容易。我猜想最简单的事情将是一个类型上的标志,如果auto试图推断它,那么这个标志只会出错,但即使这样也会很复杂,因为decltype也可以推导出它以及模板参数推导。所有这三个都以相同的方式定义,但你可能不希望后者失败。

只要适当地记录你的图书馆,并希望没有人试图捕捉他们的方式。

+0

如果x是'T&',是否有防止static_cast (x)有效的方法?我问,因为命名的临时对象在稍后使用时似乎变成'T',如果它们可以被阻止成为'std :: move'd或以其他方式转换为'T &&'该洞可以被关闭。 – Clinton 2012-03-02 06:08:27

+1

临时命名是l值,所以它们应该成为l值引用。如果'static_cast '对所有'T'无效,则转发和移动将失败。所以不,没有办法阻止转发和移动。再次,你将不得不依靠用户不要破坏你的代码。或者只是不使用表达式模板。 – 2012-03-02 06:27:34

1

正如我所说的,您的问题的根源在于表达式模板临时可能会引用其他临时对象的引用/指针。 并且通过使用自动& &我们只扩展表达式模板临时本身的寿命,但不延长其引用临时表的寿命。 是不是?例如,this您的情况是?

#include <iostream> 
#include <deque> 
#include <algorithm> 
#include <utility> 
#include <memory> 
using namespace std; 

deque<bool> pool; 

class ExpressionTemp; 
class Scalar 
{ 
    bool *alive; 

    friend class ExpressionTemp; 

    Scalar(const Scalar&); 
    Scalar &operator=(const Scalar&); 
    Scalar &operator=(Scalar&&); 
public: 
    Scalar() 
    { 
     pool.push_back(true); 
     alive=&pool.back(); 
    } 
    Scalar(Scalar &&rhs) 
     : alive(0) 
    { 
     swap(alive,rhs.alive); 
    } 
    ~Scalar() 
    { 
     if(alive) 
      (*alive)=false; 
    } 
}; 
class ExpressionTemp 
{ 
    bool *operand_alive; 
public: 
    ExpressionTemp(const Scalar &s) 
     : operand_alive(s.alive) 
    { 
    } 
    void do_job() 
    { 
     if(*operand_alive) 
      cout << "captured operand is alive" << endl; 
     else 
      cout << "captured operand is DEAD!" << endl; 
    } 
}; 

ExpressionTemp expression(const Scalar &s) 
{ 
    return {s}; 
} 
int main() 
{ 
    { 
     expression(Scalar()).do_job(); // OK 
    } 
    { 
     Scalar lv; 
     auto &&rvref=expression(lv); 
     rvref.do_job(); // OK, lv is still alive 
    } 
    { 
     auto &&rvref=expression(Scalar()); 
     rvref.do_job(); // referencing to dead temporary 
    } 
    return 0; 
} 

如果是,那么可能的解决方案之一,是让特殊的持有从临时工移动资源表达式模板的临时的。

例如,检查this的方法(您可以定义BUG_CASE宏,以获得bug的情况)。

//#define BUG_CASE 

#include <iostream> 
#include <deque> 
#include <algorithm> 
#include <utility> 
#include <memory> 
using namespace std; 

deque<bool> pool; 

class ExpressionTemp; 
class Scalar 
{ 
    bool *alive; 

    friend class ExpressionTemp; 

    Scalar(const Scalar&); 
    Scalar &operator=(const Scalar&); 
    Scalar &operator=(Scalar&&); 
public: 
    Scalar() 
    { 
     pool.push_back(true); 
     alive=&pool.back(); 
    } 
    Scalar(Scalar &&rhs) 
     : alive(0) 
    { 
     swap(alive,rhs.alive); 
    } 
    ~Scalar() 
    { 
     if(alive) 
      (*alive)=false; 
    } 
}; 
class ExpressionTemp 
{ 
#ifndef BUG_CASE 
    unique_ptr<Scalar> resource; // can be in separate type 
#endif 
    bool *operand_alive; 
public: 
    ExpressionTemp(const Scalar &s) 
     : operand_alive(s.alive) 
    { 
    } 
#ifndef BUG_CASE 
    ExpressionTemp(Scalar &&s) 
     : resource(new Scalar(move(s))), operand_alive(resource->alive) 
    { 
    } 
#endif 
    void do_job() 
    { 
     if(*operand_alive) 
      cout << "captured operand is alive" << endl; 
     else 
      cout << "captured operand is DEAD!" << endl; 
    } 
}; 

template<typename T> 
ExpressionTemp expression(T &&s) 
{ 
    return {forward<T>(s)}; 
} 
int main() 
{ 
    { 
     expression(Scalar()).do_job(); // OK, Scalar is moved to temporary 
    } 
    { 
     Scalar lv; 
     auto &&rvref=expression(lv); 
     rvref.do_job(); // OK, lv is still alive 
    } 
    { 
     auto &&rvref=expression(Scalar()); 
     rvref.do_job(); // OK, Scalar is moved into rvref 
    } 
    return 0; 
} 

您的运营商/函数重载可以返回different types,这取决于对T & & /常量牛逼&参数:

#include <iostream> 
#include <ostream> 
using namespace std; 

int test(int&&) 
{ 
    return 1; 
} 
double test(const int&) 
{ 
    return 2.5; 
}; 

int main() 
{ 
    int t; 
    cout << test(t) << endl; 
    cout << test(0) << endl; 
    return 0; 
} 

所以,当你的表情模板暂时没有资源从临时工移动 - 这是大小不会受到影响。

+0

从技术上讲,'auto' *是问题的根源。您通常可以隐藏“private”成员后面的表达式模板类型。这不是“难拼类型”;编译器会*阻止*明确使用类型。问题是'auto'和'decltype'''''''''''''' public/private''''''''''''',所以您可以创建类型,否则您不会使用类型名称。 – 2012-10-21 15:21:12

+0

好吧,我看到 - 汽车已经破坏了“私人”保护层,从而保护了更多的根本性问题。但在例如问题 - http://ideone.com/7i3yT中,auto &&可以用ExpressionTemplate &&替换。 – 2012-10-21 16:54:14