2016-03-03 17 views
12

Điều này nghe giống như một câu hỏi cơ bản, nhưng tôi không tìm thấy bất kỳ câu trả lời toàn diện nào, do đó, ở đây. Xem xét đoạn mã này:Lưu trữ tham chiếu const vào một đối tượng trong lớp

struct A { 
    const std::string& s; 
    A(const std::string& s) : s(s) {} 
}; 

int main() { 
    A a("abc"); 
    std::cout << a.s << std::endl; 
    return 0; 
} 

Demo.

Miễn là tôi hiểu, đây là UB. Chuỗi chữ "abc" liên kết với const std::string& trong hàm tạo, tạo đối tượng chuỗi tạm thời. Nó cũng bị ràng buộc để tham chiếu a.s và bị phá hủy khi a được tạo. Đó là, tham chiếu const không thể kéo dài tuổi thọ chuỗi. Dangling tham khảo, bùng nổ. Trong trường hợp cụ thể này, tôi thấy không có đầu ra nào cả trên ideone.com, nhưng bất cứ điều gì có thể xảy ra (nhớ velociraptors).

Ok, điều này là rõ ràng. Nhưng nếu điều này thực sự là ý định của chúng tôi: chúng tôi muốn lưu trữ một tham chiếu const vào một đối tượng? Để một hiện tại, không phải tạm thời? Điều này nghe có vẻ như một nhiệm vụ rất tự nhiên, nhưng tôi đã đưa ra chỉ có một (gần như) giải pháp tự nhiên cho nó. Chấp nhận lập luận constructor bằng std::reference_wrapper thay vì bằng cách tham khảo:

A(std::reference_wrapper<const std::string> r) : s(r) {} 

Kể từ std::reference_wrapper đã xóa nhà xây dựng từ temporaries:

reference_wrapper(T&& x) = delete; 

công trình này giống như mong đợi. Tuy nhiên, điều này không hoàn toàn thanh lịch. Một cách tiếp cận khác mà tôi có thể nghĩ là chấp nhận tham chiếu chuyển tiếp T&& và từ chối mọi thứ ngoại trừ chuỗi const l-value với std::enable_if. Điều này thậm chí còn kém thanh lịch hơn, tôi nghĩ vậy.

Bất kỳ phương pháp tiếp cận nào khác?

UPD Một câu hỏi khác: đây có phải là cách sử dụng hợp pháp của std::reference_wrapper hoặc có thể được coi là quá cụ thể không?

+1

Bất kỳ lý do nào tại sao 'A' không thể chỉ có' ​​std :: string' thông thường? 'A :: s' có khả năng bị treo lơ lửng ngay cả khi nó nhận được một giá trị và tham chiếu phá vỡ toán tử gán. Điều này có một trường hợp sử dụng hoặc là nó hoàn toàn học tập? – nwp

+3

'A (const std :: string &&) = xóa;' có thể làm những gì bạn muốn. Tôi vẫn không nghĩ đó là một ý tưởng hay. – nwp

+0

@nwp Điều này có thể hữu ích trong các lớp tiện ích, nơi chúng tôi kiểm soát toàn bộ các trường hợp của nó và có thể đảm bảo rằng chúng sẽ không tồn tại đối tượng được truyền. Tôi đã sử dụng 'std :: string' để biểu thị một đối tượng đắt tiền để sao chép. Một giải pháp thay thế, an toàn hơn là lưu trữ 'shared_ptr' trên đối tượng này. Tuy nhiên, điều này có thể là quá mức cần thiết trong các tình huống đơn giản. – Mikhail

Trả lời

4

tôi muốn nói là giải pháp tự nhiên sẽ làm những gì reference_wrapper làm: ngăn chặn xây dựng từ temporaries:

struct A { 
    const std::string& s; 
    A(const std::string& s) : s(s) {} 
    A(std::string&&) = delete; 
}; 

Bạn cũng nên nhớ rằng có một thành viên dữ liệu kiểu tham chiếu làm cho lớp phi chuyển nhượng (thậm chí không thể chuyển nhượng được có thể) theo mặc định, và nó thường khó thực hiện một toán tử gán. Bạn nên cân nhắc việc lưu trữ một con trỏ thay vì tham chiếu:

struct A { 
    const std::string* s; 
    A(const std::string& s) : s(&s) {} 
    A(std::string&&) = delete; 
}; 
+1

Câu trả lời hay. Nhược điểm là nếu chúng ta có nhiều đối số như vậy, sẽ rất khó để 'xóa' tất cả các mùi vị không mong muốn. – Mikhail

+1

Tôi không thích cách tiếp cận con trỏ - nó không có thông tin cú pháp mà nó không thể là nullptr. – Mikhail

+0

@Mikhail Tôi xem xét một superioir không hoàn hảo, nhưng có thể sử dụng đối với một super tinh khiết, nhưng không thể sử dụng ;-) Tuy nhiên, bạn có thể sử dụng một cái gì đó như 'not_null ' từ [Core Guideline library] (https://github.com/isocpp /CppCoreGuidelines/blob/master/CppCoreGuidelines.md) – Angew

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