2012-05-07 34 views
6

Việc sử dụng toán tử gán chuyển của std :: string (trong VC11) là gì?Di chuyển: nó sẽ làm gì?

Tôi hy vọng nó sẽ được sử dụng tự động vì v là không cần thiết sau khi chuyển nhượng nữa. Là std :: di chuyển cần thiết trong trường hợp này? Nếu vậy, tôi cũng có thể sử dụng trao đổi không phải C++ 11.

#include <string> 

struct user_t 
{ 
    void set_name(std::string v) 
    { 
     name_ = v; 
     // swap(name_, v); 
     // name_ = std::move(v); 
    } 

    std::string name_; 
}; 

int main() 
{ 
    user_t u; 
    u.set_name("Olaf"); 
    return 0; 
} 
+2

Tôi không thấy một hàm tạo di chuyển ở bất kỳ đâu? – Corbin

+0

@NicolBolas: Điều đó không liên quan đến từ xa đối với câu hỏi này. Anh ấy hỏi về việc di chuyển một thành viên 'std :: string' của lớp mình. Anh ấy không cố gắng di chuyển lớp. –

+0

@MooingDuck: Oh. Vâng, không bao giờ tâm trí sau đó. Tôi thực sự không thể làm gì nhiều về cuộc bầu cử gần đây ... –

Trả lời

11

Tôi hy vọng nó sẽ được sử dụng tự động vì v là không cần thiết sau khi chuyển nhượng nữa. Là std :: di chuyển cần thiết trong trường hợp này?

Chuyển động luôn phải được khai báo rõ ràng cho giá trị, trừ khi chúng được trả lại (theo giá trị) từ một hàm.

Điều này ngăn việc vô tình di chuyển thứ gì đó. Hãy nhớ rằng: chuyển động là hành động phá hoại; bạn không muốn nó xảy ra.

Ngoài ra, sẽ thật lạ nếu ngữ nghĩa của name_ = v; thay đổi dựa trên việc đây có phải là dòng cuối cùng trong hàm hay không. Xét cho cùng, đây là mã hoàn toàn hợp pháp:

name_ = v; 
v[0] = 5; //Assuming v has at least one character. 

Tại sao dòng đầu tiên thực hiện bản sao đôi khi và di chuyển vào các thời điểm khác?

Nếu có, tôi cũng có thể sử dụng trao đổi không phải C++ 11.

Bạn có thể làm theo ý muốn, nhưng std::move rõ ràng hơn theo ý định. Chúng tôi biết ý nghĩa của nó và những gì bạn đang làm với nó.

+0

Di chuyển về cơ bản là tối ưu hóa bản sao. Do đó, nó sẽ được tốt đẹp nếu trình biên dịch tự động thực hiện tối ưu hóa này nếu nó phát hiện nó an toàn để làm như vậy. – XTF

+0

@XTF: Điều đó sẽ gây khó khăn cho việc biết mã nào sẽ thực hiện, cho dù nó sẽ gọi một bản sao hoặc di chuyển constructor. Elision đã làm một cái gì đó tương tự, nhưng nó sẽ xảy ra chỉ trong những hoàn cảnh rất cụ thể. Và thậm chí sau đó, khi việc cắt bỏ không xảy ra, bạn vẫn biết chính xác nhà xây dựng nào được gọi. –

+0

Quy tắc bạn cần lưu ý ở đây là bất kỳ thứ gì có tên là một chuyển động lvalue và ngầm chỉ có thể được thực hiện để xác định giá trị. –

5

lấy chuỗi theo giá trị. Tuy nhiên, bên trong set_name, v là một giá trị. Hãy đối xử với những trường hợp riêng biệt:

user_t u; 
std::string str("Olaf"); // Creates string by copying a char const*. 
u.set_name(std::move(str)); // Moves string. 

Bên set_name bạn gọi toán tử gán của std::string, mà phải gánh chịu một bản sao không cần thiết. Nhưng cũng có một rvalue overload của operator=, có ý nghĩa hơn trong trường hợp của bạn là:

void set_name(std::string v) 
{ 
    name_ = std::move(v); 
} 

Bằng cách này, việc sao chép chỉ diễn ra là chuỗi constrution (std::string("Olaf")).

+0

Di chuyển đối số được truyền theo giá trị ở đây không có ý nghĩa gì. Bạn đang tránh một bản sao, nhưng bạn đã sao chép chuỗi ban đầu vào đối số được truyền theo giá trị. – mfontanini

+1

@fontanini: đó là một lựa chọn phổ biến bởi vì nó khá hiệu quả cho cả hai đi qua rvalue và lvalue cả hai, trong một chức năng. –

+3

Để mở rộng câu trả lời của @ MooingDuck: Bằng cách chỉ sử dụng ngữ nghĩa giá trị, tùy thuộc vào * người gọi * để quyết định xem có nên di chuyển hay sao chép hay không. – mavam

7

Câu trả lời được chấp nhận là câu trả lời hay (và tôi đã upvoted nó). Nhưng tôi muốn giải quyết câu hỏi này chi tiết hơn một chút:

Cốt lõi của câu hỏi của tôi là: Tại sao nó không tự động chuyển nhượng nhiệm vụ ? Trình biên dịch biết v không được sử dụng sau khi chuyển nhượng , phải không? Hoặc không C++ 11 không yêu cầu trình biên dịch phải là thông minh?

Khả năng này được xem xét trong quá trình thiết kế ngữ nghĩa di chuyển.Ở mức độ cao nhất, bạn có thể muốn trình biên dịch thực hiện một số phân tích tĩnh và di chuyển từ các đối tượng bất cứ khi nào có thể:

void set_name(std::string v) 
{ 
    name_ = v; // move from v if it can be proven that some_event is false? 
    if (some_event) 
     f(v); 
} 

Cuối cùng yêu cầu loại phân tích này từ trình biên dịch rất phức tạp. Một số trình biên dịch có thể làm bằng chứng, và một số khác thì không. Do đó dẫn đến mã không thực sự di động.

Ok, vậy còn một số trường hợp đơn giản hơn nếu không có báo cáo thì sao?

void foo() 
{ 
    X x; 
    Y y(x); 
    X x2 = x; // last use? move? 
} 

Vâng, rất khó để biết nếu y.~Y() sẽ thấy x đã được chuyển từ. Và nói chung:

void foo() 
{ 
    X x; 
    // ... 
    // lots of code here 
    // ... 
    X x2 = x; // last use? move? 
} 

nó là khó khăn đối với trình biên dịch để phân tích này để biết liệu x là thực sự không còn được sử dụng sau khi xây dựng bản sao cho x2.

Vì vậy, bản gốc "di chuyển" đề nghị đã đưa ra một quy tắc cho di chuyển ngầm rằng thật sự rất đơn giản, và rất bảo thủ:

lvalues ​​chỉ có thể được ngầm chuyển từ trong trường hợp sao chép sự bỏ bớt là đã cho phép.

Ví dụ:

#include <cassert> 

struct X 
{ 
    int i_; 
    X() : i_(1) {} 
    ~X() {i_ = 0;} 
}; 

struct Y 
{ 
    X* x_; 
    Y() : x_(0) {} 
    ~Y() {assert(x_ != 0); assert(x_->i_ != 0);} 
}; 

X foo(bool some_test) 
{ 
    Y y; 
    X x; 
    if (some_test) 
    { 
     X x2; 
     return x2; 
    } 
    y.x_ = &x; 
    return x; 
} 

int main() 
{ 
    X x = foo(false); 
} 

Ở đây, bằng C++ 98/03 quy tắc, chương trình này có thể hoặc có thể không khẳng định, tuỳ thuộc vào việc hay không sao chép sự bỏ bớt tại return x xảy ra. Nếu nó xảy ra, chương trình chạy tốt. Nếu nó không xảy ra, chương trình sẽ khẳng định.

Và do đó, nó đã được lý luận: Khi RVO được cho phép, chúng tôi đã ở trong một khu vực không có bảo đảm về giá trị của x. Vì vậy, chúng ta sẽ có thể tận dụng lợi thế của việc này và di chuyển từ x. Nguy cơ có vẻ nhỏ và lợi ích được xem là lớn. Điều này không chỉ có nghĩa là nhiều chương trình hiện có sẽ trở nên nhanh hơn nhiều với một biên dịch lại đơn giản, nhưng nó cũng có nghĩa là bây giờ chúng ta có thể trả về các kiểu "di chuyển" từ các chức năng của nhà máy. Đây là một lợi ích rất lớn đối với tỷ lệ rủi ro.

Cuối quá trình chuẩn hóa, chúng tôi có chút tham lam và cũng cho rằng di chuyển ngầm xảy ra khi trả về tham số giá trị (và loại khớp với kiểu trả về). Những lợi ích có vẻ tương đối lớn ở đây quá, mặc dù cơ hội phá vỡ mã là lớn hơn một chút vì đây không phải là trường hợp RVO (hoặc là) hợp pháp. Nhưng tôi không có một cuộc biểu tình phá mã cho trường hợp này.

Vì vậy, cuối cùng, câu trả lời cho câu hỏi cốt lõi của bạn là thiết kế ban đầu của ngữ nghĩa di chuyển đã có một lộ trình rất bảo thủ đối với việc phá vỡ mã hiện có. Nếu không, nó chắc chắn sẽ bị bắn hạ trong ủy ban. Cuối quá trình này, đã có một vài thay đổi khiến thiết kế tích cực hơn một chút. Nhưng vào thời điểm này, đề xuất cốt lõi đã được củng cố vững chắc trong tiêu chuẩn với sự hỗ trợ đa số (nhưng không nhất trí).

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