2012-02-26 38 views
29

Khi thực hiện nhà xây dựng di chuyển và toán tử gán di chuyển, người ta thường viết mã như thế này:Tại sao di chuyển một biến con trỏ không đặt nó thành null?

p = other.p; 
other.p = 0; 

Các hoạt động di chuyển ngầm định sẽ được thực hiện với mã như thế này:

p = std::move(other.p); 

nào sẽ là sai lầm, vì việc di chuyển một biến con trỏ không không phải là đặt biến đó thành rỗng. Tại sao vậy? Có trường hợp nào chúng tôi muốn các hoạt động di chuyển rời khỏi biến con trỏ ban đầu không thay đổi?

Lưu ý: Bằng cách "di chuyển", tôi làm không chỉ có nghĩa là biểu thức con std::move(other.p), ý tôi là toàn bộ biểu thức p = std::move(other.p). Vì vậy, tại sao không có quy tắc ngôn ngữ đặc biệt nào nói rằng "Nếu phía bên tay phải của một nhiệm vụ là con trỏ xvalue, nó được đặt thành null sau khi việc gán đã diễn ra."?

+5

Tại sao phải có? Điều duy nhất bạn phải làm với một đối tượng 'di chuyển' là bỏ qua nó. Có một con trỏ trỏ đến bộ nhớ không thuộc sở hữu của lớp không phải là một vấn đề nếu bạn không còn sử dụng con trỏ, phải không? – hvd

+5

"Bạn không trả tiền cho những gì bạn không sử dụng"? –

+7

@hvd: The destructor chắc chắn sẽ không bỏ qua nó nếu nó nói 'delete p' :) – fredoverflow

Trả lời

27

Thiết lập một con trỏ thô để null sau khi di chuyển nó ngụ ý rằng con trỏ đại diện cho quyền sở hữu. Tuy nhiên, rất nhiều con trỏ được sử dụng để biểu diễn các mối quan hệ. Hơn nữa, trong một thời gian dài, các mối quan hệ sở hữu được thể hiện khác với việc sử dụng một con trỏ thô. Ví dụ: mối quan hệ sở hữu mà bạn đang đề cập được đại diện bởi std::unique_ptr<T>. Nếu bạn muốn các hoạt động di chuyển ngầm được tạo ra chăm sóc quyền sở hữu của bạn, tất cả những gì bạn cần làm là sử dụng các thành viên thực sự đại diện (và thực hiện) hành vi sở hữu mong muốn.

Ngoài ra, hành vi của các hoạt động di chuyển được tạo phù hợp với những gì đã được thực hiện với thao tác sao chép: chúng cũng không đưa ra bất kỳ giả định quyền sở hữu nào và không thực hiện ví dụ: một bản sao sâu nếu một con trỏ được sao chép. Nếu bạn muốn điều này xảy ra, bạn cũng cần phải tạo một lớp thích hợp mã hóa các ngữ nghĩa có liên quan.

+3

Tôi chỉ nhớ đọc một bài báo cho biết "di chuyển không bao giờ nên đắt hơn sao chép" hoặc một cái gì đó như thế. Đặt con trỏ ban đầu null sẽ phá vỡ quy tắc đó. Bạn có biết tôi đang nói về bài báo nào không, hoặc bộ não của tôi có làm được điều đó không? – fredoverflow

+1

+1. Lời giải thích hay. Tốt hơn tôi! – Nawaz

4

Tôi nghĩ câu trả lời là: việc thực hiện một hành vi như vậy là khá nhiều và do đó Standard không cảm thấy cần phải áp đặt bất kỳ quy tắc nào trên trình biên dịch. Ngôn ngữ C++ là rất lớn và không phải tất cả mọi thứ có thể được tưởng tượng trước khi sử dụng nó. Lấy ví dụ, mẫu của C++. Nó không được thiết kế đầu tiên để được sử dụng theo cách nó được sử dụng ngày nay (nghĩa là khả năng lập trình meta). Vì vậy, tôi nghĩ rằng, tiêu chuẩn chỉ cho tự do, và không thực hiện bất kỳ quy tắc cụ thể cho std::move(other.p), sau đây là một trong những nguyên tắc thiết kế: "Bạn không trả tiền cho những gì bạn không sử dụng".

Mặc dù, std::unique_ptr có thể di chuyển được, mặc dù không thể sao chép được. Vì vậy, nếu bạn muốn con trỏ-ngữ nghĩa đó là di chuyển và copyable cả hai, thì đây là một trong những thực hiện tầm thường:

template<typename T> 
struct movable_ptr 
{ 
    T *pointer; 
    movable_ptr(T *ptr=0) : pointer(ptr) {} 
    movable_ptr<T>& operator=(T *ptr) { pointer = ptr; return *this; } 
    movable_ptr(movable_ptr<T> && other) 
    { 
     pointer = other.pointer; 
     other.pointer = 0; 
    } 
    movable_ptr<T>& operator=(movable_ptr<T> && other) 
    { 
     pointer = other.pointer; 
     other.pointer = 0; 
     return *this; 
    } 
    T* operator->() const { return pointer; } 
    T& operator*() const { return *pointer; } 

    movable_ptr(movable_ptr<T> const & other) = default; 
    movable_ptr<T> & operator=(movable_ptr<T> const & other) = default; 
}; 

Bây giờ bạn có thể viết các lớp học, mà không cần viết riêng của bạn di chuyển-ngữ nghĩa:

struct T 
{ 
    movable_ptr<A> aptr; 
    movable_ptr<B> bptr; 
    //... 

    //and now you could simply say 
    T(T&&) = default; 
    T& operator=(T&&) = default; 
}; 

Note rằng bạn vẫn phải viết bản sao ngữ nghĩa và phá hủy, như movable_ptrkhông phải là con trỏ thông minh.

+0

Nhưng các toán tử di chuyển được xác định ngầm sẽ không gọi 'move_Ptr' ... – fredoverflow

+3

@Fred - Nó sẽ nếu bạn sử dụng một con trỏ đủ thông minh thay vì lưu trữ các con trỏ thô. Có thể đó là vấn đề? –

+0

@BoPersson: Tôi nghĩ rằng đó phải là câu trả lời? – Nawaz

0

Ví dụ: nếu bạn có con trỏ đến đối tượng được chia sẻ. Hãy nhớ rằng, sau khi di chuyển một đối tượng phải ở trạng thái nhất quán trong nội bộ, do đó, thiết lập một con trỏ không được null thành giá trị null là không chính xác.

Ie:

struct foo 
{ 
    bar* shared_factory; // This can be the same for several 'foo's 
         // and must never null. 
}; 

Sửa

Dưới đây là một trích dẫn về MoveConstructibe từ tiêu chuẩn:

T u = rv; 
... 
rv’s state is unspecified [ Note:rv must still meet the requirements 
of the library component that is using it. The operations listed in 
those requirements must work as specified whether rv has been moved 
from or not. 
+0

Nhưng đặt biến con trỏ thành null sẽ chỉ xảy ra trên xvalues. Khách hàng sẽ không thể kiểm tra con trỏ null sau đó. – fredoverflow

+0

Điều gì sẽ xảy ra nếu phá hủy của bạn, ví dụ, sử dụng con trỏ bằng cách nào đó và sẽ gọi UB nếu nó là null? Ngoài ra, tôi không thể sao lưu yêu cầu của tôi với một trích dẫn từ tiêu chuẩn, nhưng theo như tôi nhớ, sau khi di chuyển đối tượng phải còn hiệu lực (trong đó bạn có thể tiếp tục sử dụng nó bình thường), không chỉ phá hủy. – doublep

+0

@FredOverflow: tìm thấy báo giá có liên quan trong tiêu chuẩn. – doublep

5

Di chuyển hiển thị đối tượng được chuyển từ "không hợp lệ". Nó không không sẽ tự động đặt trạng thái ở trạng thái "trống" an toàn. Theo nguyên tắc lâu dài của C++ là "không trả tiền cho những gì bạn không sử dụng", đó là công việc của bạn nếu bạn muốn.

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