7

Tôi có một lớp 'nhớ' một tham chiếu đến một số đối tượng (ví dụ: một biến số nguyên). Tôi không thể để nó tham chiếu đến một giá trị bị phá hủy ngay lập tức, và tôi đang tìm một cách để bảo vệ người dùng của lớp của tôi khỏi làm như vậy một cách tình cờ.ngăn chặn pass-by-ref của đối tượng tạm thời

Có phải quá tải tham chiếu rvalue là cách tốt để ngăn chặn tạm thời được chuyển vào không?

struct HasRef { 
    int& a; 
    HasRef(int& a):a(a){} 
    void foo(){ a=1; } 
}; 


int main(){ 
    int x=5; 
    HasRef r1(x); 
    r1.foo(); // works like intended. 

    HasRef r2(x+4); 
    r2.foo(); // dereferences the temporary created by x+4 

} 

Liệu quá tải rvalue tư nhân có thực hiện?

struct HasRef { 
    int& a; 
    HasRef(int& a):a(a){} 
    void foo(){ a=1; } 
private: 
    HasRef(int&& a); 
}; 

... HasRef r2(x+1); // doesn't compile => problem solved? 

Có bất kỳ cạm bẫy nào mà tôi không thấy không?

+4

Tồn tại tạm thời không liên kết với một tài liệu tham khảo giá trị trái. Định nghĩa của 'r2' trong ví dụ đầu tiên của bạn không nên biên dịch. – musiphil

+0

Nếu bạn đang sử dụng VC++, một giải pháp là để bật lên mức cảnh báo và nó sẽ cho bạn biết khi nào nó không hoạt động. –

+4

Tuy nhiên, một tham chiếu _const_ sẽ liên kết với một tạm thời, vì vậy câu hỏi vẫn là một câu hỏi hay. Tôi đã xem xét cách tiếp cận này, nhưng tôi vẫn cho rằng nếu một lớp sẽ lưu trữ một tham chiếu (hoặc con trỏ) đến đối tượng được tham chiếu, thì tốt hơn là lấy một con trỏ trong hàm tạo, bit rõ ràng hơn (khi một nhà xây dựng có một con trỏ, thường nó làm cho tôi suy nghĩ hai lần về những gì đối tượng sẽ làm gì với nó). –

Trả lời

2

Điều đó không nên biên dịch. Một trình biên dịch C++ tốt (hoặc thực sự hầu như bất kỳ trình biên dịch C++ nào mà tôi từng thấy) sẽ dừng điều đó xảy ra.

+0

cảm ơn; có vẻ như tôi phải xem xét kỹ hơn mã của mình và nói lại vấn đề. – xtofl

3

Bỏ qua thực tế mã không hợp lệ và chỉ cần trả lời các câu hỏi về tình trạng quá tải tư nhân ...

Trong C++ 11 Tôi muốn một chức năng xóa một chức năng riêng. Rõ ràng hơn là bạn thực sự không thể gọi nó (ngay cả khi bạn là thành viên hoặc bạn bè của lớp học.)

N.B. nếu các nhà xây dựng đã xóa là HasRef(int&&)=delete nó sẽ không được lựa chọn ở đây:

int i; 
HasRef hr(std::forward<const int>(i)); 

Với một đối số kiểu const int&& các nhà xây dựng HasRef(const int&) sẽ được sử dụng, không phải là một HasRef(int&&). Trong trường hợp này nó sẽ là OK, vì i thực sự là một giá trị trái, nhưng nhìn chung có thể không phải là trường hợp, vì vậy đây có thể là một trong những lần hiếm hoi khi một tài liệu tham khảo const rvalue rất hữu ích:

HasRef(const int&&) = delete; 
0

Tôi đoán bạn đang biên dịch trong MSVS. Trong trường hợp đó, hãy tắt tiện ích mở rộng ngôn ngữ và bạn sẽ gặp lỗi.

Nếu không, thậm chí không đánh dấu tham chiếu const sẽ kéo dài tuổi thọ của tạm thời cho đến khi hàm tạo kết thúc. Sau đó, bạn sẽ tham chiếu đến một đối tượng không hợp lệ.

+0

Nó dễ nói hơn làm. Vô hiệu hóa các tiện ích mở rộng ngôn ngữ với MSVC khiến thư viện chuẩn của chúng bị khiếu nại. –

+0

@AlexandreC. Tôi đã không có vấn đề cho đến nay ... may mắn? –

+0

Có thể. Tôi nhớ lại đã có vấn đề với MSVC2005. –

2

Nếu bạn có để lưu trữ một tham chiếu const đối với một số thể hiện của loại B vào lớp học của bạn A, thì chắc chắn bạn muốn được đảm bảo, rằng tuổi thọ của A dụ sẽ được vượt quá bởi tuổi thọ của B dụ:

B b{}; 
A a1{b}; // allowed 
A a2{B{}}; // should be denied 
B const f() { return B{}; } // const result type may make sense for user-defined types 
A a3{f()}; // should also be denied! 

Để làm cho nó có thể bạn nên rõ ràng để = delete; tất cả các quá tải constructor, có thể chấp nhận rvalues ​​(cả hai const &&&&). Để đạt được điều này, bạn chỉ nên = delete; chỉ const && phiên bản của hàm tạo.

struct B {}; 

struct A 
{ 
    B const & b; 
    A(B const & bb) : b(bb) { ; } // accepts only `B const &` and `B &` 
    A(B const &&) = delete; // prohibits both `B &&` and `B const &&` 
}; 

Cách tiếp cận này cho phép bạn cấm chuyển cho nhà xây dựng tất cả các loại giá trị.

Điều này cũng hoạt động đối với các vô hướng tích hợp.Ví dụ, double const f() { return 0.01; }, mặc dù nó gây ra một cảnh báo như:

cảnh báo: 'const' loại vòng trên kiểu trả về không có tác dụng [-Wignored-vòng loại]

nó vẫn có thể có tác dụng nếu bạn chỉ = delete; chỉ && phiên bản của constructor:

struct A 
{ 
    double const & eps; 
    A(double const & e) : eps(e) {} // binds to `double const &`, `double &` AND ! `double const &&` 
    A(double &&) = delete; // prohibit to binding only to `double &&`, but not to `double const &&` 
}; 

double const get_eps() { return 0.01; } 

A a{0.01}; // hard error 
A a{get_eps()}; // no hard error, but it is wrong! 

đối với nhà xây dựng không chuyển đổi (tức là không unary) có một vấn đề: bạn có thể phải cung cấp = delete; phiên bản -d cho tất cả các phiên bản combinatorically có thể có của nhà xây dựng như sau:

struct A 
{ 
    A(B const &, C const &) {} 
    A(B const &&, C const &&) = delete; 
    // and also! 
    A(B const &, C const &&) = delete; 
    A(B const &&, C const &) = delete; 
}; 

cấm hỗn hợp các trường hợp như:

B b{}; 
A a{b, C{}}; 
+0

Điểm tốt, các nhà thầu đa arg! Có lẽ nó sẽ làm cho tinh thần để làm cho tất cả các params templated, và tĩnh khẳng định tất cả là tài liệu tham khảo lvalue bằng cách nào đó. Điều đó sẽ ngăn chặn hỗn hợp nổ. – xtofl

+0

@xtofl Đơn giản 'static_assert' không phải là một sự lựa chọn tốt, khi bạn áp dụng một đặc điểm kiểu như' std :: is_constructible' thành 'A'. Bạn cần phải SFINAE-out kết hợp xấu hoặc sử dụng * Concept Lite * nếu có sẵn. – Orient

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