Tài liệu tham khảo rvalue cho * này là gì?


238

Đã xem qua một đề xuất có tên là "tham chiếu giá trị cho * this" trong trang trạng thái C ++ 11 của clang .

Tôi đã đọc khá nhiều về các tài liệu tham khảo giá trị và hiểu chúng, nhưng tôi không nghĩ rằng tôi biết về điều này. Tôi cũng không thể tìm thấy nhiều tài nguyên trên web bằng các thuật ngữ.

Có một liên kết đến tài liệu đề xuất trên trang: N2439 (Mở rộng ngữ nghĩa di chuyển sang * này), nhưng tôi cũng không nhận được nhiều ví dụ từ đó.

Tính năng này là gì?

Câu trả lời:


293

Đầu tiên, "giới thiệu vòng loại cho * này" chỉ là một "tuyên bố tiếp thị". Các loại *thiskhông bao giờ thay đổi, xem dưới cùng của bài viết này. Đó là cách dễ dàng hơn để hiểu nó với từ ngữ này mặc dù.

Tiếp theo, đoạn mã sau chọn hàm được gọi dựa trên bộ định mức ref của "tham số đối tượng ẩn" của hàm :

// t.cpp
#include <iostream>

struct test{
  void f() &{ std::cout << "lvalue object\n"; }
  void f() &&{ std::cout << "rvalue object\n"; }
};

int main(){
  test t;
  t.f(); // lvalue
  test().f(); // rvalue
}

Đầu ra:

$ clang++ -std=c++0x -stdlib=libc++ -Wall -pedantic t.cpp
$ ./a.out
lvalue object
rvalue object

Toàn bộ điều được thực hiện để cho phép bạn tận dụng thực tế khi đối tượng mà hàm được gọi là một giá trị (ví dụ tạm thời không được đặt tên). Lấy mã sau đây làm ví dụ khác:

struct test2{
  std::unique_ptr<int[]> heavy_resource;

  test2()
    : heavy_resource(new int[500]) {}

  operator std::unique_ptr<int[]>() const&{
    // lvalue object, deep copy
    std::unique_ptr<int[]> p(new int[500]);
    for(int i=0; i < 500; ++i)
      p[i] = heavy_resource[i];

    return p;
  }

  operator std::unique_ptr<int[]>() &&{
    // rvalue object
    // we are garbage anyways, just move resource
    return std::move(heavy_resource);
  }
};

Điều này có thể là một chút giả định, nhưng bạn nên có ý tưởng.

Lưu ý rằng bạn có thể kết hợp các vòng loại cv ( constvolatile) và vòng loại ref ( &&&).


Lưu ý: Nhiều trích dẫn tiêu chuẩn và giải thích quá tải giải quyết sau đây!

Để hiểu cách thức hoạt động của điều này và tại sao câu trả lời của @Nicol Bolas ít nhất là sai một phần, chúng tôi phải khai thác tiêu chuẩn C ++ một chút (phần giải thích tại sao câu trả lời của @ Nicol sai ở phía dưới, nếu bạn là chỉ quan tâm đến điều đó).

Hàm nào sẽ được gọi được xác định bởi một quá trình gọi là giải quyết quá tải . Quá trình này khá phức tạp, vì vậy chúng tôi sẽ chỉ chạm vào bit quan trọng đối với chúng tôi.

Đầu tiên, điều quan trọng là phải xem cách giải quyết quá tải cho các chức năng thành viên:

§13.3.1 [over.match.funcs]

p2 Tập hợp các hàm ứng cử viên có thể chứa cả các hàm thành viên và không phải thành viên sẽ được giải quyết theo cùng một danh sách đối số. Vì vậy, danh sách tham số và tham số có thể so sánh được trong tập hợp không đồng nhất này, một hàm thành viên được coi là có một tham số phụ, được gọi là tham số đối tượng ẩn, đại diện cho đối tượng mà hàm thành viên được gọi . [...]

p3 Tương tự, khi thích hợp, bối cảnh có thể xây dựng một danh sách đối số có chứa một đối số ngụ ý để biểu thị đối tượng được vận hành trên đó.

Tại sao chúng ta thậm chí cần so sánh các chức năng thành viên và không thành viên? Toán tử quá tải, đó là lý do tại sao. Xem xét điều này:

struct foo{
  foo& operator<<(void*); // implementation unimportant
};

foo& operator<<(foo&, char const*); // implementation unimportant

Bạn chắc chắn muốn sau đây gọi chức năng miễn phí, phải không?

char const* s = "free foo!\n";
foo f;
f << s;

Đó là lý do tại sao các chức năng thành viên và không phải thành viên được bao gồm trong cái gọi là tập quá tải. Để làm cho độ phân giải ít phức tạp hơn, phần in đậm của trích dẫn tiêu chuẩn tồn tại. Ngoài ra, đây là bit quan trọng đối với chúng tôi (cùng một mệnh đề):

p4 Đối với các hàm thành viên không tĩnh, loại tham số đối tượng ẩn là

  • Tham chiếu lvalue cho cv Hướng dẫn X cho các chức năng được khai báo mà không cần vòng loại hoặc với & vòng loại

  • “Tài liệu tham khảo rvalue để cv X ” cho các chức năng khai báo với các && ref-vòng loại

Trường hợp Xchức năng là thành viên và cv là tiêu chuẩn cv trên khai báo hàm thành viên. [...]

p5 Trong quá trình phân giải quá tải [...] [t] anh ta ẩn tham số đối tượng [...] giữ lại danh tính của mình vì các chuyển đổi trên đối số tương ứng sẽ tuân theo các quy tắc bổ sung sau:

  • không có đối tượng tạm thời nào có thể được giới thiệu để giữ đối số cho tham số đối tượng ẩn; và

  • không có chuyển đổi do người dùng xác định có thể được áp dụng để đạt được loại khớp với nó

[...]

(Bit cuối cùng chỉ có nghĩa là bạn không thể gian lận độ phân giải quá tải dựa trên chuyển đổi ngầm định của đối tượng mà một hàm thành viên (hoặc toán tử) được gọi.)

Hãy lấy ví dụ đầu tiên ở đầu bài này. Sau khi chuyển đổi đã nói ở trên, bộ quá tải trông giống như thế này:

void f1(test&); // will only match lvalues, linked to 'void test::f() &'
void f2(test&&); // will only match rvalues, linked to 'void test::f() &&'

Sau đó, danh sách đối số, chứa đối số ngụ ý , được khớp với danh sách tham số của mọi hàm có trong tập quá tải. Trong trường hợp của chúng tôi, danh sách đối số sẽ chỉ chứa đối số đối tượng đó. Chúng ta hãy xem nó trông như thế nào:

// first call to 'f' in 'main'
test t;
f1(t); // 't' (lvalue) can match 'test&' (lvalue reference)
       // kept in overload-set
f2(t); // 't' not an rvalue, can't match 'test&&' (rvalue reference)
       // taken out of overload-set

Nếu, sau khi tất cả các quá tải trong tập hợp được kiểm tra, chỉ còn lại một, độ phân giải quá tải đã thành công và hàm được liên kết với quá tải được chuyển đổi đó được gọi. Điều tương tự cũng xảy ra với cuộc gọi thứ hai đến 'f':

// second call to 'f' in 'main'
f1(test()); // 'test()' not an lvalue, can't match 'test&' (lvalue reference)
            // taken out of overload-set
f2(test()); // 'test()' (rvalue) can match 'test&&' (rvalue reference)
            // kept in overload-set

Tuy nhiên, xin lưu ý rằng, nếu chúng tôi không cung cấp bất kỳ vòng loại giới thiệu nào (và như vậy không bị quá tải chức năng), thì điều đó f1 sẽ phù hợp với một giá trị (vẫn §13.3.1):

p5 [...] Đối với các hàm thành viên không tĩnh được khai báo mà không có vòng loại giới thiệu , áp dụng quy tắc bổ sung:

  • ngay cả khi tham số đối tượng ẩn không constđủ tiêu chuẩn, một giá trị có thể được liên kết với tham số miễn là trong tất cả các khía cạnh khác, đối số có thể được chuyển đổi thành loại tham số đối tượng ẩn.
struct test{
  void f() { std::cout << "lvalue or rvalue object\n"; }
};

int main(){
  test t;
  t.f(); // OK
  test().f(); // OK too
}

Bây giờ, tại sao câu trả lời của @ Nicol ít nhất là sai. Anh ta nói:

Lưu ý rằng tuyên bố này thay đổi loại *this.

Đó là sai lầm, *thisluôn luôn một giá trị trái:

§5.3.1 [expr.unary.op] p1

Các unary *Thực hiện điều hành gián tiếp : các biểu hiện mà nó được áp dụng sẽ là một con trỏ đến một loại đối tượng, hoặc một con trỏ đến một loại chức năng và kết quả là một giá trị trái đề cập đến đối tượng hoặc chức năng mà các điểm biểu.

§9.3.2 [class.this] p1

Trong phần thân của hàm thành viên không tĩnh (9.3), từ khóa thislà biểu thức prvalue có giá trị là địa chỉ của đối tượng mà hàm được gọi. Loại thistrong một chức năng thành viên của một lớp XX*. [...]


Tôi tin rằng các loại paraneter ngay sau phần "sau khi chuyển đổi" phải là 'foo' thay vì 'test'.
ryaner

@ryaner: Tìm tốt, cảm ơn bạn. Mặc dù không phải là tham số nhưng định danh lớp của các hàm là sai. :)
Xèo

Rất tiếc, tôi đã quên lớp học đồ chơi có tên là test khi tôi đọc phần đó và nghĩ rằng f được chứa trong foo do đó nhận xét của tôi ..
ryaner

Điều này có thể được thực hiện với các nhà xây dựng : MyType(int a, double b) &&?
Germán Diago

2
"Loại * điều này không bao giờ thay đổi" Bạn có thể nên rõ ràng hơn một chút rằng nó không thay đổi dựa trên tiêu chuẩn r / l-value. nhưng nó có thể thay đổi giữa const / non-const.
xaxxon

78

Có một trường hợp sử dụng bổ sung cho mẫu vòng loại lvalue. C ++ 98 có ngôn ngữ cho phép các consthàm không phải thành viên được gọi cho các thể hiện của lớp là các giá trị. Điều này dẫn đến tất cả các loại kỳ lạ chống lại chính khái niệm về tính hợp lệ và sai lệch so với cách các loại tích hợp hoạt động:

struct S {
  S& operator ++(); 
  S* operator &(); 
};
S() = S();      // rvalue as a left-hand-side of assignment!
S& foo = ++S(); // oops, dangling reference
&S();           // taking address of rvalue...

Lvalue giới thiệu vòng loại giải quyết những vấn đề sau:

struct S {
  S& operator ++() &;
  S* operator &() &;
  const S& operator =(const S&) &;
};

Bây giờ các toán tử hoạt động giống như các kiểu dựng sẵn, chỉ chấp nhận giá trị.


28

Giả sử bạn có hai hàm trên một lớp, cả hai đều có cùng tên và chữ ký. Nhưng một trong số họ được tuyên bố const:

void SomeFunc() const;
void SomeFunc();

Nếu một thể hiện lớp là không const, độ phân giải quá tải sẽ ưu tiên chọn phiên bản không phải là const. Nếu là trường hợp const, người dùng chỉ có thể gọi constphiên bản. Và thiscon trỏ là mộtconst con trỏ, vì vậy thể hiện không thể thay đổi.

"Tham chiếu giá trị r cho cái này là gì" cho phép bạn thêm một lựa chọn khác:

void RValueFunc() &&;

Điều này cho phép bạn có một chức năng chỉ có thể được gọi nếu người dùng gọi nó thông qua một giá trị r thích hợp. Vì vậy, nếu điều này là trong loại Object:

Object foo;
foo.RValueFunc(); //error: no `RValueFunc` version exists that takes `this` as l-value.
Object().RValueFunc(); //calls the non-const, && version.

Bằng cách này, bạn có thể chuyên môn hóa hành vi dựa trên việc đối tượng có được truy cập thông qua giá trị r hay không.

Lưu ý rằng bạn không được phép quá tải giữa các phiên bản tham chiếu giá trị r và các phiên bản không tham chiếu. Đó là, nếu bạn có tên hàm thành viên, tất cả các phiên bản của nó đều sử dụng vòng loại l / r-value thishoặc không ai trong số chúng làm được. Bạn không thể làm điều này:

void SomeFunc();
void SomeFunc() &&;

Bạn phải làm điều này:

void SomeFunc() &;
void SomeFunc() &&;

Lưu ý rằng tuyên bố này thay đổi loại *this. Điều này có nghĩa là &&tất cả các phiên bản đều truy cập thành viên dưới dạng tham chiếu giá trị r. Vì vậy, nó có thể dễ dàng di chuyển từ bên trong đối tượng. Ví dụ được đưa ra trong phiên bản đầu tiên của đề xuất là (lưu ý: những điều sau đây có thể không đúng với phiên bản cuối cùng của C ++ 11; nó đi thẳng từ đề xuất "r-value from this" ban đầu):

class X {
   std::vector<char> data_;
public:
   // ...
   std::vector<char> const & data() const & { return data_; }
   std::vector<char> && data() && { return data_; }
};

X f();

// ...
X x;
std::vector<char> a = x.data(); // copy
std::vector<char> b = f().data(); // move

2
Tôi nghĩ bạn cần std::movephiên bản thứ hai, không? Ngoài ra, tại sao các giá trị tham chiếu rvalue trở lại?
Xèo

1
@Xeo: Bởi vì đó là những gì ví dụ trong đề xuất; Tôi không biết nếu nó vẫn hoạt động với phiên bản hiện tại. Và lý do cho lợi nhuận tham chiếu giá trị r là bởi vì chuyển động nên tùy thuộc vào người nắm bắt nó. Điều đó chưa xảy ra, chỉ trong trường hợp anh ta thực sự muốn lưu trữ nó trong && thay vì giá trị.
Nicol Bolas

3
Phải, tôi nghĩ về lý do cho câu hỏi thứ hai của mình. Mặc dù vậy, tôi tự hỏi, liệu một tham chiếu giá trị cho thành viên của một tạm thời kéo dài thời gian của tạm thời, hoặc thành viên của nó? Tôi có thể thề rằng tôi đã thấy một câu hỏi về điều đó trên SO một thời gian trước đây ...
Xeo

1
@Xeo: Điều đó không hoàn toàn đúng. Độ phân giải quá tải sẽ luôn chọn phiên bản không phải là const nếu nó tồn tại. Bạn sẽ cần phải thực hiện một dàn diễn viên để có được phiên bản const. Tôi đã cập nhật bài viết để làm rõ.
Nicol Bolas

15
Tôi nghĩ rằng tôi có thể giải thích, sau tất cả tôi đã tạo ra tính năng này cho C ++ 11;) Xeo đã đúng khi khẳng định rằng nó không thay đổi loại *this, tuy nhiên tôi có thể hiểu sự nhầm lẫn đến từ đâu. Điều này là do trình phân loại tham chiếu thay đổi loại tham số hàm ẩn (hoặc "ẩn") mà đối tượng "này" (trích dẫn được đặt vào mục đích ở đây!) Bị ràng buộc trong quá trình giải quyết quá tải và gọi hàm. Vì vậy, không có thay đổi nào *thisvì điều này được khắc phục như Xeo giải thích. Thay vào đó, thay đổi tham số "hidupt" để làm cho nó tham chiếu giá trị hoặc tham chiếu giá trị, giống như constvòng loại hàm làm cho nó constv.v.
bronekk
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.