2012-03-02 53 views
9

Tôi hiểu rằng làm một cái gì đó như sau:Ngăn chặn biểu mẫu ràng buộc để rvalue tham chiếu

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

có gây ra một lỗi runtime im lặng nếu các hoạt động ma trận sử dụng các mẫu biểu thức (chẳng hạn như boost::ublas).

Có cách nào thiết kế mẫu biểu thức để ngăn trình biên dịch biên dịch mã như vậy có thể dẫn đến việc sử dụng các thời gian hết hạn đã hết hạn khi chạy không?

(Tôi đã cố gắng không thành công để làm việc xung quanh vấn đề này, nỗ lực là here)

+3

Nếu bạn cấm ràng buộc như vậy, 'toán tử + (expression_template const &, expression_template const &)' cũng sẽ không biên dịch. –

+0

@ R.MartinhoFernandes: Tại sao 'toán tử +' lấy các đối số của nó bằng 'expression_template const &'? Tôi có thể tưởng tượng rằng 'toán tử +' có thể lấy các đối số của nó thông qua một số loại proxy mà sẽ vẫn không cho phép 'tham chiếu const' bị ràng buộc không chắc chắn với các mẫu biểu thức. (Tôi không nói điều đó là có thể, nhưng ít nhất cũng không phải là không thể). – Mankarse

+0

@Mankarse Bạn không thể kết hợp chuyển đổi ngầm định và khấu trừ loại mẫu. Vì bạn phải chọn loại khấu trừ cho 'toán tử +' để làm việc, các đối số cho nó phải là kiểu của mẫu biểu thức. (Trừ khi tôi hiểu lầm ý bạn là "một số loại proxy") –

Trả lời

7

Có cách nào để thiết kế các mẫu biểu để ngăn chặn các trình biên dịch từ biên dịch mã như vậy có thể dẫn đến việc sử dụng thời gian hết hạn trong thời gian chạy?

No. Điều này thực sự được công nhận trước tiêu chuẩn cuối cùng của C++ 11, nhưng tôi không biết liệu nó có được đưa đến thông báo của ủy ban hay không. Không phải là một sửa chữa sẽ dễ dàng. Tôi cho rằng điều đơn giản nhất sẽ là một lá cờ trên các loại đơn giản sẽ là lỗi nếu auto cố gắng suy luận nó, nhưng thậm chí điều đó sẽ phức tạp vì decltype cũng có thể suy ra nó, cũng như khấu trừ đối số mẫu. Và cả ba thứ này đều được định nghĩa theo cùng một cách, nhưng bạn có thể không muốn cái sau thất bại.

Chỉ cần tài liệu thư viện của bạn một cách thích hợp và hy vọng rằng không ai cố gắng chụp chúng theo cách đó.

+0

Có cách nào để ngăn static_cast (x) không hợp lệ nếu x là 'T &' không? Tôi hỏi vì các thời gian được đặt tên dường như trở thành 'T &' s khi được sử dụng sau này, nếu chúng có thể được ngăn chặn từ 'std :: move'd hoặc được chuyển đổi thành' T && 'lỗ có thể được đóng lại. – Clinton

+1

Thời gian được đặt tên là các giá trị l, do đó chúng sẽ trở thành tham chiếu giá trị l. Và nếu 'static_cast ' không hợp lệ cho tất cả 'T &' s, thì việc chuyển tiếp và di chuyển sẽ không thành công. Vì vậy, không, không có cách nào để phá vỡ chuyển tiếp và di chuyển. Một lần nữa, bạn sẽ phải dựa vào người dùng để không phá vỡ mã của bạn. Hoặc không sử dụng các mẫu biểu thức. –

1

Như tôi chưa hiểu, gốc của vấn đề của bạn là mẫu biểu thức tạm thời có thể có tham chiếu/con trỏ đến một số thời gian khác. Và bằng cách sử dụng tự động & &, chúng tôi chỉ mở rộng vòng đời của biểu mẫu tạm thời, nhưng không phải là thời gian tồn tại của thời gian mà nó có tham chiếu đến. Có đúng không?

Ví dụ: this trường hợp của bạn?

#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; 
} 

Nếu có thì một trong các giải pháp có thể là tạo các loại biểu mẫu đặc biệt tạm thời giữ tài nguyên được chuyển từ trạng thái tạm thời.

Ví dụ: hãy kiểm tra phương pháp this (bạn có thể xác định macro BUG_CASE, để nhận lại trường hợp lỗi).

//#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; 
} 

của bạn quá tải toán tử/chức năng có thể trở lại different types, tùy thuộc vào T & &/const T & đối số:

#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; 
} 

Vì vậy, khi bạn mẫu biểu tạm thời không có nguồn lực chuyển từ temporaries - đó là kích thước sẽ không bị ảnh hưởng.

+0

Về mặt kỹ thuật, 'auto' * là * gốc của vấn đề. Bạn thường có thể ẩn kiểu mẫu biểu thức đằng sau các thành viên 'private'. Nó không phải là "khó đánh vần loại"; trình biên dịch sẽ * ngăn chặn * bạn sử dụng loại một cách rõ ràng. Vấn đề ở đây là 'auto' và' decltype' ở bên cạnh toàn bộ 'public/private', cho phép bạn tạo các kiểu mà bạn không thể, miễn là bạn không bao giờ thực sự sử dụng tên kiểu của nó. –

+0

Ok, tôi thấy - tự động có lớp bảo vệ "riêng tư" bị hỏng, bảo vệ vấn đề cơ bản hơn. Nhưng ví dụ như trong câu hỏi được hỏi - http://ideone.com/7i3yT, auto && có thể được thay thế bằng ExpressionTemplate &&. –

Các vấn đề liên quan