Các tham chiếu rvalue đến const có bất kỳ công dụng nào không?


Câu trả lời:


78

Chúng đôi khi hữu ích. Bản thân dự thảo C ++ 0x sử dụng chúng ở một số nơi, ví dụ:

template <class T> void ref(const T&&) = delete;
template <class T> void cref(const T&&) = delete;

Hai quá tải trên đảm bảo rằng các hàm khác ref(T&)và các cref(const T&)hàm không liên kết với các giá trị (nếu không sẽ có thể).

Cập nhật

Tôi vừa kiểm tra tiêu chuẩn chính thức N3290 , rất tiếc là tiêu chuẩn này không được cung cấp công khai và nó có trong 20.8 Đối tượng chức năng [function.objects] / p2:

template <class T> void ref(const T&&) = delete;
template <class T> void cref(const T&&) = delete;

Sau đó, tôi đã kiểm tra bản nháp sau C ++ 11 gần đây nhất, có sẵn công khai, N3485 và trong 20.8 Đối tượng chức năng [function.objects] / p2, nó vẫn cho biết:

template <class T> void ref(const T&&) = delete;
template <class T> void cref(const T&&) = delete;

Nhìn vào cppreference, có vẻ như đây không phải là trường hợp nữa. Bất kỳ ý tưởng tại sao? Bất kỳ nơi nào khác const T&&được sử dụng?
Pubby 13/12/12

58
Tại sao bạn lại đưa cùng một mã vào câu trả lời của mình ba lần? Tôi đã cố gắng tìm ra sự khác biệt quá lâu.
typ1232

9
@ typ1232: Có vẻ như tôi đã cập nhật câu trả lời gần 2 năm sau khi tôi trả lời nó, do lo ngại trong các nhận xét rằng các hàm được tham chiếu không còn xuất hiện nữa. Tôi đã sao chép / dán từ N3290 và từ bản nháp cuối cùng là N3485 để cho thấy rằng các chức năng vẫn xuất hiện. Theo suy nghĩ của tôi vào thời điểm đó, sử dụng copy / paste là cách tốt nhất để đảm bảo rằng nhiều người hơn tôi có thể xác nhận rằng tôi không bỏ qua một số thay đổi nhỏ trong những chữ ký này.
Howard Hinnant

1
@kevinarpe: "Quá tải khác" (không hiển thị ở đây, nhưng trong tiêu chuẩn) lấy tham chiếu giá trị và không bị xóa. Quá tải được hiển thị ở đây phù hợp hơn cho các giá trị hơn là quá tải lấy tham chiếu giá trị. Vì vậy, các đối số rvalue liên kết với các quá tải được hiển thị ở đây, và sau đó gây ra lỗi thời gian biên dịch vì các quá tải này bị xóa.
Howard Hinnant

1
Việc sử dụng const T&&ngăn chặn ai đó sử dụng một cách ngu ngốc các args mẫu rõ ràng của biểu mẫu ref<const A&>(...). Đó không phải là một lập luận quá mạnh mẽ, nhưng chi phí của việc const T&&vượt qua T&&là khá nhỏ.
Howard Hinnant,

6

Ngữ nghĩa của việc nhận tham chiếu const rvalue (và không phải cho =delete) là để nói:

  • chúng tôi không hỗ trợ hoạt động cho các giá trị!
  • mặc dù vậy, chúng tôi vẫn sao chép , bởi vì chúng tôi không thể di chuyển tài nguyên đã chuyển, hoặc vì không có ý nghĩa thực tế nào cho việc "di chuyển" nó.

Trường hợp sử dụng sau đây có thể là IMHO một trường hợp sử dụng tốt để tham chiếu giá trị tới const , mặc dù ngôn ngữ đã quyết định không sử dụng cách tiếp cận này (xem bài đăng gốc SO ).


Trường hợp: hàm tạo con trỏ thông minh từ con trỏ thô

Nó thường được khuyến khích sử dụng make_uniquemake_shared, nhưng cả hai unique_ptrvà đều shared_ptrcó thể được xây dựng từ một con trỏ thô. Cả hai hàm tạo đều lấy con trỏ theo giá trị và sao chép nó. Cả hai đều cho phép (nghĩa là: không ngăn cản ) việc sử dụng liên tục con trỏ ban đầu được truyền cho chúng trong hàm tạo.

Các biên dịch mã sau và kết quả với miễn phí gấp đôi :

int* ptr = new int(9);
std::unique_ptr<int> p { ptr };
// we forgot that ptr is already being managed
delete ptr;

Cả hai unique_ptrvà đều shared_ptrcó thể ngăn chặn điều trên nếu các hàm tạo có liên quan của chúng mong đợi nhận được con trỏ thô dưới dạng const rvalue , ví dụ: for unique_ptr:

unique_ptr(T* const&& p) : ptr{p} {}

Trong trường hợp đó, đoạn mã miễn phí kép ở trên sẽ không biên dịch, nhưng đoạn mã sau sẽ:

std::unique_ptr<int> p1 { std::move(ptr) }; // more verbose: user moves ownership
std::unique_ptr<int> p2 { new int(7) };     // ok, rvalue

Lưu ý rằng nó ptrvẫn có thể được sử dụng sau khi nó được di chuyển, vì vậy lỗi tiềm ẩn không hoàn toàn biến mất. Nhưng nếu người dùng được yêu cầu gọi std::movemột lỗi như vậy sẽ rơi vào quy tắc chung là: không sử dụng tài nguyên đã được di chuyển.


Người ta có thể hỏi: OK, nhưng tại sao lại là T* const&& p ?

Lý do rất đơn giản, để cho phép tạo unique_ptr từ con trỏ const . Hãy nhớ rằng tham chiếu const rvalue chung chung hơn là chỉ tham chiếu rvalue vì nó chấp nhận cả constnon-const. Vì vậy, chúng tôi có thể cho phép những điều sau:

int* const ptr = new int(9);
auto p = std::unique_ptr<int> { std::move(ptr) };

điều này sẽ không xảy ra nếu chúng ta chỉ mong đợi tham chiếu rvalue (lỗi biên dịch: không thể ràng buộc const rvalue với rvalue ).


Nhưng dù sao, điều này là quá muộn để đề xuất một điều như vậy. Nhưng ý tưởng này trình bày một cách sử dụng hợp lý tham chiếu rvalue đến const .


Tôi nằm trong số những người có ý tưởng tương tự, nhưng tôi không có sự hỗ trợ để thúc đẩy.
Red.Wave

"auto p = std :: unique_ptr {std :: move (ptr)};" không biên dịch với lỗi "khấu trừ đối số mẫu lớp không thành công". Tôi nghĩ nó phải là "unique_ptr <int>".
Zehui Lin

4

Chúng được cho phép và thậm chí là các hàm được xếp hạng dựa trên const, nhưng vì bạn không thể di chuyển từ đối tượng const được giới thiệu const Foo&&nên chúng không hữu ích.


Chính xác thì ý bạn là gì về nhận xét "được xếp hạng"? Tôi đoán vậy phải làm gì với giải quyết quá tải?
fredoverflow

Tại sao bạn không thể di chuyển từ const rvalue-ref, nếu kiểu đã cho có một move ctor nhận const rvalue-ref?
Fred Nurk

6
@FredOverflow, thứ hạng về tình trạng quá tải là thế này:const T&, T&, const T&&, T&&
Gene Bushuyev

2
@Fred: Làm thế nào để bạn di chuyển mà không cần sửa đổi nguồn?
fredoverflow

3
@Fred: Các thành viên dữ liệu có thể thay đổi, hoặc có thể di chuyển theo kiểu giả định này không yêu cầu sửa đổi các thành viên dữ liệu.
Fred Nurk

2

Bên cạnh std :: ref , thư viện chuẩn cũng sử dụng tham chiếu const rvalue trong std :: as_const cho cùng mục đích.

template <class T>
void as_const(const T&&) = delete;

Nó cũng được sử dụng làm giá trị trả về trong std :: tùy chọn khi nhận giá trị được bọc:

constexpr const T&& operator*() const&&;
constexpr const T&& value() const &&;

Cũng như trong std :: get :

template <class T, class... Types>
constexpr const T&& get(const std::variant<Types...>&& v);
template< class T, class... Types >
constexpr const T&& get(const tuple<Types...>&& t) noexcept;

Điều này có lẽ là để duy trì loại giá trị cũng như tính không đổi của trình bao bọc khi truy cập giá trị được bao bọc.

Điều này tạo ra sự khác biệt liệu các hàm đủ điều kiện ref const rvalue có thể được gọi trên đối tượng được bọc hay không. Điều đó nói rằng, tôi không biết bất kỳ cách sử dụng nào cho các hàm đủ điều kiện của const rvalue ref.


1

Tôi không thể nghĩ đến tình huống mà điều này sẽ hữu ích trực tiếp, nhưng nó có thể được sử dụng gián tiếp:

template<class T>
void f(T const &x) {
  cout << "lvalue";
}
template<class T>
void f(T &&x) {
  cout << "rvalue";
}

template<class T>
void g(T &x) {
  f(T());
}

template<class T>
void h(T const &x) {
  g(x);
}

T trong g là T const, vì vậy f 's x là T const &&.

Có khả năng điều này dẫn đến lỗi comile trong f (khi nó cố gắng di chuyển hoặc sử dụng đối tượng), nhưng f có thể nhận một rvalue-ref để nó không thể được gọi trên các giá trị, mà không cần sửa đổi giá trị (như trong quá trình đơn giản ví dụ trên).

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.