2015-02-18 54 views
66

Trong C++ 11, chúng ta có thể viết mã này:Tại sao chúng ta có thể sử dụng `std :: move` trên đối tượng` const`?

struct Cat { 
    Cat(){} 
}; 

const Cat cat; 
std::move(cat); //this is valid in C++11 

khi tôi gọi std::move, nó có nghĩa là tôi muốn di chuyển đối tượng, ví dụ: Tôi sẽ thay đổi đối tượng. Để di chuyển đối tượng const là không hợp lý, vậy tại sao std::move không hạn chế hành vi này? Nó sẽ là một cái bẫy trong tương lai, phải không?

Đây bẫy nghĩa như Brandon đề cập trong các bình luận:

"Tôi nghĩ anh ấy có nghĩa là nó 'bẫy' anh ấy lén lút lén lút vì nếu anh ta không nhận ra, ông kết thúc với một bản sao mà không phải là những gì ông dự định.

Trong cuốn sách 'Effective Modern C++' Scott Meyers, ông đưa ra một ví dụ:

class Annotation { 
public: 
    explicit Annotation(const std::string text) 
    : value(std::move(text)) //here we want to call string(string&&), 
           //but because text is const, 
           //the return type of std::move(text) is const std::string&& 
           //so we actually called string(const string&) 
           //it is a bug which is very hard to find out 
private: 
    std::string value; 
}; 

Nếu std::move bị cấm từ hoạt động trên một đối tượng const, chúng ta có thể dễ dàng tìm ra các lỗi, phải không ?

+2

Nhưng hãy thử di chuyển. Cố gắng thay đổi trạng thái của nó. 'std :: move' tự nó không làm gì với đối tượng. Người ta có thể tranh cãi 'std :: move' được đặt tên kém. – juanchopanza

+2

Nó không thực sự * di chuyển * bất cứ điều gì. Tất cả nó được đúc thành một tham chiếu rvalue. thử 'CAT cat2 = std :: move (cat);', giả sử 'CAT' hỗ trợ chuyển động thường xuyên. – WhozCraig

+7

'std :: move' chỉ là một diễn viên, nó không thực sự di chuyển bất cứ điều gì –

Trả lời

38
struct strange { 
    mutable size_t count = 0; 
    strange(strange const&& o):count(o.count) { o.count = 0; } 
}; 

const strange s; 
strange s2 = std::move(s); 

đây chúng ta thấy một sử dụng std::move trên T const. Nó trả về một T const&&. Chúng tôi có một nhà xây dựng di chuyển cho strange có chính xác loại này.

Và nó được gọi.

Bây giờ, đúng là loại kỳ lạ này hiếm hơn các lỗi mà đề xuất của bạn sẽ khắc phục.

Nhưng, mặt khác, hiện tại std::move hoạt động tốt hơn trong mã chung, nơi bạn không biết loại bạn đang làm việc có là T hoặc T const.

+1

+1 là câu trả lời đầu tiên thực sự cố gắng giải thích lý do tại sao bạn sẽ _want_ gọi 'std :: move' trên đối tượng' const'. –

74

Có một mẹo ở đây bạn đang nhìn, mà cụ thể là std::move(cat)không thực sự di chuyển bất cứ điều gì. Nó chỉ yêu cầu trình biên dịch thử để di chuyển. Tuy nhiên, vì lớp của bạn không có hàm tạo nào chấp nhận một số const CAT&&, thay vào đó, nó sẽ sử dụng hàm tạo bản sao ngụ ý const CAT& ẩn và sao chép an toàn. Không nguy hiểm, không có cái bẫy. Nếu hàm tạo bản sao bị tắt vì bất kỳ lý do nào, bạn sẽ gặp lỗi trình biên dịch.

struct CAT 
{ 
    CAT(){} 
    CAT(const CAT&) {std::cout << "COPY";} 
    CAT(CAT&&) {std::cout << "MOVE";} 
}; 

int main() { 
    const CAT cat; 
    CAT cat2 = std::move(cat); 
} 

in COPY, không MOVE.

http://coliru.stacked-crooked.com/a/0dff72133dbf9d1f

Lưu ý rằng các lỗi trong đoạn code bạn đề cập đến là một buổi biểu diễn vấn đề , không phải là một ổn định vấn đề, do đó, một lỗi như vậy sẽ không gây ra một vụ tai nạn, bao giờ hết. Nó sẽ chỉ sử dụng một bản sao chậm hơn. Ngoài ra, một lỗi như vậy cũng xảy ra đối với các đối tượng không phải là const không có các hàm tạo di chuyển, vì vậy chỉ cần thêm quá tải const sẽ không bắt được tất cả chúng. Chúng tôi có thể kiểm tra khả năng di chuyển cấu trúc hoặc di chuyển gán từ loại tham số, nhưng điều đó sẽ ảnh hưởng đến mã mẫu chung là được cho là để quay lại trình tạo bản sao. Và heck, có thể ai đó muốn có thể xây dựng từ const CAT&&, tôi là ai để nói rằng anh ta không thể?

+0

Đã bị ẩn. Đáng chú ý là việc xóa bỏ bản sao ngầm định của bản sao-ctor khi định nghĩa do người dùng định nghĩa của nhà xây dựng hoặc nhà điều hành chuyển giao thường xuyên cũng sẽ chứng minh điều này thông qua việc biên dịch bị hỏng. Câu trả lời hay. – WhozCraig

+0

Cũng đáng nói đến là một nhà xây dựng bản sao cần một giá trị không phải là 'const' cũng sẽ không giúp ích gì. [class.copy] §8: "Nếu không, bản sao được khai báo ngầm sẽ có dạng ' X :: X (X &) '" – Deduplicator

+6

Tôi không nghĩ anh ta có nghĩa là "bẫy" trong điều khoản máy tính/lắp ráp. Tôi nghĩ anh ấy có nghĩa là "bẫy" anh ta lén lút lén lút bởi vì nếu anh ta không nhận ra, anh ta kết thúc bằng một bản sao không phải là những gì anh ta dự định. Tôi đoán .. – Brandon

16

Một lý do khiến phần còn lại của câu trả lời bị bỏ qua cho đến nay là khả năng mã hóa chung linh hoạt khi di chuyển. Ví dụ cho phép nói rằng tôi muốn viết một hàm tổng quát mà chuyển tất cả các yếu tố ra khỏi một loại thùng chứa để tạo ra một loại container với các giá trị như nhau:

template <class C1, class C2> 
C1 
move_each(C2&& c2) 
{ 
    return C1(std::make_move_iterator(c2.begin()), 
       std::make_move_iterator(c2.end())); 
} 

mát, bây giờ tôi tương đối có thể tạo hiệu quả một vector<string> từ một deque<string> và mỗi cá nhân string sẽ được chuyển trong quá trình này.

Nhưng nếu tôi muốn di chuyển từ một map?

int 
main() 
{ 
    std::map<int, std::string> m{{1, "one"}, {2, "two"}, {3, "three"}}; 
    auto v = move_each<std::vector<std::pair<int, std::string>>>(m); 
    for (auto const& p : v) 
     std::cout << "{" << p.first << ", " << p.second << "} "; 
    std::cout << '\n'; 
} 

Nếu std::move khăng khăng đòi một const lập luận phi, instantiation trên của move_each sẽ không biên dịch vì nó đang cố gắng để di chuyển một const int (các key_type của map). Nhưng mã này không quan tâm nếu không thể di chuyển key_type. Nó muốn di chuyển mapped_type (std::string) vì lý do hiệu suất.

Đó là ví dụ này, và vô số các ví dụ khác như mã hóa chung là std::move là yêu cầu để di chuyển, không phải là yêu cầu di chuyển.

1

Tôi có mối quan tâm giống như OP.

std :: di chuyển không di chuyển một đối tượng, không đảm bảo các đối tượng là di chuyển. Vậy tại sao nó được gọi là di chuyển?

Tôi nghĩ là không di chuyển có thể là một trong hai kịch bản sau đây:

1. Các loại di chuyển là const.

Lý do chúng tôi có từ khóa const trong ngôn ngữ là chúng tôi muốn trình biên dịch ngăn chặn bất kỳ thay đổi nào đối với một đối tượng được xác định là const. Đưa ra ví dụ trong sách của Scott Meyers:

class Annotation { 
    public: 
    explicit Annotation(const std::string text) 
    : value(std::move(text)) // "move" text into value; this code 
    { … } // doesn't do what it seems to!  
    … 
    private: 
    std::string value; 
    }; 

Nghĩa đen là gì? Di chuyển một chuỗi const đến thành viên giá trị - ít nhất, đó là sự hiểu biết của tôi trước khi tôi đọc lời giải thích.

Nếu ngôn ngữ có ý định không làm di chuyển hoặc không đảm bảo di chuyển được áp dụng khi std :: move() được gọi, sau đó nó là nghĩa đen gây hiểu nhầm khi sử dụng từ di chuyển.

Nếu ngôn ngữ khuyến khích mọi người sử dụng std :: di chuyển để có hiệu quả tốt hơn, nó phải ngăn chặn bẫy như thế này càng sớm càng tốt, đặc biệt là đối với loại mâu thuẫn rõ ràng này.

Tôi đồng ý rằng mọi người nên lưu ý di chuyển một hằng số là không thể, nhưng nghĩa vụ này không nên bao hàm trình biên dịch có thể im lặng khi mâu thuẫn hiển nhiên sẽ xảy ra.

2.Các đối tượng không có constructor di chuyển

Cá nhân, tôi nghĩ rằng đây là một câu chuyện riêng biệt từ mối quan tâm OP, như Chris Drew nói

@hvd Đó có vẻ như một chút của một tổ chức phi tranh cãi với tôi. Chỉ vì đề xuất của OP không sửa chữa tất cả các lỗi trên thế giới không nhất thiết có nghĩa đó là một ý tưởng tồi (có thể là, nhưng không phải vì lý do bạn đưa ra). - Chris Drew

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