Sự khác biệt giữa std :: move và std :: Forward


160

Tôi đã thấy điều này ở đây: Move Con constructor gọi lớp cơ sở Move Con constructor

Ai đó có thể giải thích:

  1. sự khác biệt giữa std::movestd::forward, tốt nhất là với một số ví dụ mã?
  2. Làm thế nào để suy nghĩ về nó một cách dễ dàng, và khi nào sử dụng


"Cách suy nghĩ về nó một cách dễ dàng và khi nào nên sử dụng" Bạn sử dụng movekhi bạn muốn di chuyển một giá trị và forwardkhi bạn muốn sử dụng chuyển tiếp hoàn hảo. Đây không phải là khoa học tên lửa ở đây;)
Nicol Bolas

move () thực hiện cast vô điều kiện trong đó as Forward () thực hiện cast dựa trên tham số đã truyền.
RaGa__M

Câu trả lời:


159

std::movelấy một đối tượng và cho phép bạn coi nó là tạm thời (một giá trị). Mặc dù đó không phải là một yêu cầu ngữ nghĩa, thông thường, một hàm chấp nhận tham chiếu đến một giá trị sẽ làm mất hiệu lực của nó. Khi bạn nhìn thấy std::move, nó chỉ ra rằng giá trị của đối tượng không nên được sử dụng sau đó, nhưng bạn vẫn có thể gán một giá trị mới và tiếp tục sử dụng nó.

std::forwardcó một trường hợp sử dụng duy nhất: để truyền tham số hàm templated (bên trong hàm) sang danh mục giá trị (lvalue hoặc rvalue) mà người gọi đã sử dụng để truyền nó. Điều này cho phép các đối số giá trị được truyền vào dưới dạng giá trị và giá trị được truyền dưới dạng giá trị, một sơ đồ gọi là "chuyển tiếp hoàn hảo".

Để minh họa :

void overloaded( int const &arg ) { std::cout << "by lvalue\n"; }
void overloaded( int && arg ) { std::cout << "by rvalue\n"; }

template< typename t >
/* "t &&" with "t" being template param is special, and  adjusts "t" to be
   (for example) "int &" or non-ref "int" so std::forward knows what to do. */
void forwarding( t && arg ) {
    std::cout << "via std::forward: ";
    overloaded( std::forward< t >( arg ) );
    std::cout << "via std::move: ";
    overloaded( std::move( arg ) ); // conceptually this would invalidate arg
    std::cout << "by simple passing: ";
    overloaded( arg );
}

int main() {
    std::cout << "initial caller passes rvalue:\n";
    forwarding( 5 );
    std::cout << "initial caller passes lvalue:\n";
    int x = 5;
    forwarding( x );
}

Như Howard đề cập, cũng có những điểm tương đồng vì cả hai chức năng này chỉ đơn giản là chuyển sang kiểu tham chiếu. Nhưng bên ngoài các trường hợp sử dụng cụ thể này (bao gồm 99,9% tính hữu ích của các tham chiếu giá trị), bạn nên sử dụng static_casttrực tiếp và viết một lời giải thích tốt về những gì bạn đang làm.


Tôi không chắc chắn đó là chính xác rằng std::forward's chỉ trường hợp sử dụng là chuyển tiếp hoàn hảo của các đối số chức năng. Tôi đã gặp phải tình huống mà tôi muốn chuyển tiếp hoàn hảo những thứ khác, như thành viên đối tượng.
Geoff Romer

@GeoffRomer Thành viên của một tham số? Bạn có một ví dụ?
Potatoswatter

Tôi đã đăng một ví dụ dưới dạng một câu hỏi riêng biệt: stackoverflow.com/questions/20616958/ trên
Geoff Romer

Hmm, vì vậy nếu tôi hiểu điều này một cách chính xác, điều đó có nghĩa là tôi có thể viết một hàm chuyển tiếp () trong đó chuyển tiếp (5) hoạt động trên 5 như thể tôi chuyển nó theo giá trị trong đó chuyển tiếp (x) hoạt động trên x như thể tôi đã vượt qua nó tham chiếu mà không cần phải viết một cách quá tải.
iheanyi

@iheanyi Yep, đó là ý tưởng! Nhưng nó phải là một khuôn mẫu, không chỉ là một chức năng thông thường.
Potatoswatter

63

Cả hai std::forwardstd::movekhông có gì ngoài phôi.

X x;
std::move(x);

Các biểu thức trên đưa biểu thức giá trị xcủa loại X thành biểu thức giá trị của loại X (chính xác là một giá trị xvalue). movecũng có thể chấp nhận một giá trị:

std::move(make_X());

và trong trường hợp này, nó là một hàm nhận dạng: lấy một giá trị của loại X và trả về một giá trị của loại X.

Với std::forwardbạn có thể chọn đích đến một mức độ nào đó:

X x;
std::forward<Y>(x);

Chuyển biểu thức giá trị xcủa loại X thành biểu thức của loại Y. Có các ràng buộc về những gì Y có thể.

Y có thể là Cơ sở X có thể truy cập hoặc tham chiếu đến Cơ sở X. Y có thể là X hoặc tham chiếu đến X. Người ta không thể bỏ qua vòng loại cv forward, nhưng người ta có thể thêm vòng loại cv. Y không thể là loại chỉ có thể chuyển đổi từ X, ngoại trừ thông qua chuyển đổi Cơ sở có thể truy cập.

Nếu Y là tham chiếu giá trị, kết quả sẽ là biểu thức giá trị. Nếu Y không phải là tham chiếu giá trị, kết quả sẽ là biểu thức giá trị (xvalue chính xác).

forwardchỉ có thể lấy một đối số giá trị nếu Y không phải là tham chiếu giá trị. Đó là, bạn không thể đưa ra một giá trị cho lvalue. Điều này là vì lý do an toàn vì làm như vậy thường dẫn đến các tài liệu tham khảo lơ lửng. Nhưng đúc một giá trị thành rvalue là ok và được phép.

Nếu bạn cố gắng chỉ định Y cho một cái gì đó không được phép, lỗi sẽ được bắt gặp tại thời gian biên dịch, không phải thời gian chạy.


Nếu tôi hoàn toàn chuyển tiếp một đối tượng đến một hàm bằng cách sử dụng std::forward, thì sau khi hàm đó được thực thi, tôi có thể sử dụng đối tượng đó không? Tôi biết rằng, trong trường hợp std::move, đó là một hành vi không xác định.
iammilind

1
Về move: stackoverflow.com/a/7028318/576911forward, nếu bạn vượt qua một giá trị, API của bạn sẽ phản ứng như thể nó nhận được một giá trị. Thông thường điều này có nghĩa là giá trị sẽ không được sửa đổi. Nhưng nếu đó là một giá trị không phải là const, API của bạn có thể đã sửa đổi nó. Nếu bạn vượt qua một giá trị, điều này thường có nghĩa là API của bạn có thể đã di chuyển khỏi nó và do đó stackoverflow.com/a/7028318/576911 sẽ được áp dụng.
Howard Hinnant

21

std::forwardđược sử dụng để chuyển tiếp một tham số chính xác theo cách nó được truyền đến một hàm. Giống như hiển thị ở đây:

Khi nào nên sử dụng std :: Forward để chuyển tiếp đối số?

Việc sử dụng std::movecung cấp một đối tượng như một giá trị, để có thể khớp với hàm tạo di chuyển hoặc hàm chấp nhận các giá trị. Nó làm điều đó std::move(x)ngay cả khi xbản thân nó không phải là một giá trị.

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.