2013-06-14 39 views
13

Bài viết MSDN, How to: Write a Move Constuctor, có đề xuất sau đây.Thực hiện Di chuyển Constructor bằng cách gọi Di chuyển Nhà điều hành Chuyển nhượng

Nếu bạn cung cấp cả toán tử di chuyển và toán tử gán cho lớp, bạn có thể loại bỏ mã dự phòng bằng cách viết hàm tạo di chuyển để gọi toán tử gán di chuyển. Các dụ sau đây cho thấy một phiên bản sửa đổi của các nhà xây dựng di chuyển mà gọi toán tử gán di chuyển:

// Move constructor. 
MemoryBlock(MemoryBlock&& other) 
    : _data(NULL) 
    , _length(0) 
{ 
    *this = std::move(other); 
} 

là mã này không hiệu quả bằng cách gấp đôi khởi MemoryBlock giá trị 's, hoặc trình biên dịch sẽ có thể tối ưu hóa đi các khởi tạo thêm? Tôi có nên luôn luôn viết các nhà xây dựng di chuyển của mình bằng cách gọi toán tử gán di chuyển không?

Trả lời

15

tôi sẽ không làm điều đó theo cách này. Lý do cho các thành viên di chuyển để tồn tại ở nơi đầu tiên là hiệu suất. Làm điều này cho nhà xây dựng di chuyển của bạn giống như bắn phá ra megabucks cho một siêu xe và sau đó cố gắng để tiết kiệm tiền bằng cách mua khí thường xuyên.

Nếu bạn muốn giảm số lượng mã bạn viết, chỉ cần không viết thành viên di chuyển. Lớp học của bạn sẽ sao chép tốt trong ngữ cảnh di chuyển.

Nếu bạn muốn mã của mình có hiệu suất cao, hãy điều chỉnh công cụ xây dựng di chuyển của bạn và chuyển bài tập càng nhanh càng tốt. Thành viên di chuyển tốt sẽ nhanh chóng, và bạn nên ước lượng tốc độ của mình bằng cách đếm số lượt tải, cửa hàng và chi nhánh. Nếu bạn có thể viết một cái gì đó với 4 tải/cửa hàng thay vì 8, làm điều đó! Nếu bạn có thể viết một cái gì đó mà không có nhánh thay vì 1, hãy làm điều đó!

Khi bạn (hoặc khách hàng của bạn) đưa lớp học của bạn vào một số std::vector, một số di chuyển có thể được tạo trên loại của bạn. Ngay cả khi di chuyển của bạn là sét nhanh ở 8 tải/cửa hàng, nếu bạn có thể tăng tốc nhanh gấp hai lần hoặc thậm chí nhanh hơn 50% chỉ với 4 hoặc 6 lần tải/cửa hàng, thì đó là thời gian tốt.

Cá nhân tôi bị bệnh khi nhìn thấy con trỏ chờ đợi và sẵn sàng đóng góp thêm 5 phút để viết mã của tôi và biết rằng nó càng nhanh càng tốt.

Nếu bạn vẫn không thuyết phục điều này đáng giá, hãy viết cả hai cách và sau đó kiểm tra hội đồng được tạo ở mức tối ưu hóa đầy đủ. Ai biết được, trình biên dịch của bạn chỉ có thể đủ thông minh để tối ưu hóa thêm tải và cửa hàng cho bạn. Nhưng vào thời điểm này, bạn đã đầu tư nhiều thời gian hơn nếu bạn vừa viết một hàm khởi động được tối ưu hóa ngay từ đầu.

+12

Có một số lời khuyên tốt ở đây nhưng tôi nghĩ rằng bỏ qua câu trả lời này để đưa vào tài khoản rằng chi phí của mã trùng lặp là nhiều hơn chỉ 5 phút dành cho nó một lần thứ hai - mã dup có chi phí đáng kể hơn nhiều so với khả năng đọc, khả năng bảo trì và độ bền vững. Cụ thể khi có mã trùng lặp, nó mời một loại lỗi rất phổ biến: đó là, khi một cái gì đó được sửa hoặc thay đổi trong một phiên bản của mã và không phải trong một mã khác. Bị cắn bởi nhiều lần hơn tôi quan tâm trong cuộc sống của tôi, tôi sẽ không lặp lại mã trừ khi có một lý do * rất * mạnh mẽ đã được chứng minh. –

+2

Hiệu suất là một trong những lý do nhưng không phải là lý do duy nhất hoặc quan trọng nhất. Hầu hết thời gian khi tôi viết một nhà xây dựng di chuyển và điều hành chuyển nhượng di chuyển là bởi vì lớp của tôi có quyền sở hữu duy nhất của một tài nguyên. Thường thì hiệu suất không phải là một vấn đề, vì vậy trong những trường hợp này có ý nghĩa để tránh trùng lặp. – opetroch

+0

@ user779446: Cảm ơn bạn đã chia sẻ các ưu tiên của mình. Đây là của tôi: http://howardhinnant.github.io/coding_guidelines.html –

1

Tôi không nghĩ rằng bạn sẽ chú ý đến sự khác biệt về hiệu suất đáng kể. Tôi cho rằng thực hành tốt để sử dụng toán tử gán di chuyển từ hàm khởi tạo di chuyển.

Tuy nhiên tôi thà sử dụng std :: về phía trước thay vì std :: di chuyển vì nó hợp lý hơn:

*this = std::forward<MemoryBlock>(other); 
+3

'std :: forward' có nghĩa là để sử dụng trong các mẫu, khi bạn không biết bạn có thể di chuyển đối tượng hay không. – aschepler

+0

Không phải biến "khác" đã là một giá trị, vì vậy không cần std :: move? –

+3

@MikeFisher Nope, một rvalue được đặt tên là một lvalue – TiMoch

0

Tùy thuộc vào việc nhà điều hành chuyển nhượng của bạn làm gì.Nếu bạn nhìn vào một trong bài viết bạn liên quan đến, bạn sẽ thấy một phần:

// Free the existing resource. 
    delete[] _data; 

Vì vậy, trong bối cảnh này, nếu bạn gọi là toán tử gán di chuyển từ các nhà xây dựng di chuyển mà không initialising _data đầu tiên, bạn sẽ cuối cùng cố gắng xóa một con trỏ chưa được khởi tạo. Vì vậy, trong ví dụ này, không hiệu quả hay không, nó thực sự quan trọng là bạn khởi tạo các giá trị.

+0

Bạn sẽ không cần phải khởi tạo giá trị hai lần nếu bạn đã sử dụng danh sách khởi tạo': _data (other._data) ' –

+0

Không, nhưng sau đó bạn đang triển khai lại mã di chuyển, thay vì gọi toán tử gán Câu hỏi của bạn là về –

2

Phiên bản C++ 11 của tôi thuộc lớp MemoryBlock.

#include <algorithm> 
#include <vector> 
// #include <stdio.h> 

class MemoryBlock 
{ 
public: 
    explicit MemoryBlock(size_t length) 
    : length_(length), 
     data_(new int[length]) 
    { 
    // printf("allocating %zd\n", length); 
    } 

    ~MemoryBlock() noexcept 
    { 
    delete[] data_; 
    } 

    // copy constructor 
    MemoryBlock(const MemoryBlock& rhs) 
    : MemoryBlock(rhs.length_) // delegating to another ctor 
    { 
    std::copy(rhs.data_, rhs.data_ + length_, data_); 
    } 

    // move constructor 
    MemoryBlock(MemoryBlock&& rhs) noexcept 
    : length_(rhs.length_), 
     data_(rhs.data_) 
    { 
    rhs.length_ = 0; 
    rhs.data_ = nullptr; 
    } 

    // unifying assignment operator. 
    // move assignment is not needed. 
    MemoryBlock& operator=(MemoryBlock rhs) // yes, pass-by-value 
    { 
    swap(rhs); 
    return *this; 
    } 

    size_t Length() const 
    { 
    return length_; 
    } 

    void swap(MemoryBlock& rhs) 
    { 
    std::swap(length_, rhs.length_); 
    std::swap(data_, rhs.data_); 
    } 

private: 
    size_t length_; // note, the prefix underscore is reserved. 
    int* data_; 
}; 

int main() 
{ 
    std::vector<MemoryBlock> v; 
    // v.reserve(10); 
    v.push_back(MemoryBlock(25)); 
    v.push_back(MemoryBlock(75)); 

    v.insert(v.begin() + 1, MemoryBlock(50)); 
} 

Với trình biên dịch C++ 11 đúng, MemoryBlock::MemoryBlock(size_t) chỉ nên được gọi 3 lần trong chương trình thử nghiệm.

+1

Bạn nên sử dụng std :: unique_ptr thay vì xóa mảng theo cách thủ công, điều đó sẽ làm cho nó thêm C++ 11ish – TiMoch

+0

Tôi thích swap() trong toán tử gán di chuyển vì điều này đảm bảo rằng trình hủy sẽ đúng được thực hiện trên cá thể của đối tượng, do đó giải phóng tất cả các tài nguyên của đối tượng đó. –

0

Tôi chỉ đơn giản là sẽ loại bỏ thành viên khởi tạo và viết,

MemoryBlock(MemoryBlock&& other) 
{ 
    *this = std::move(other); 
} 

này sẽ luôn luôn làm việc trừ khi trường hợp ngoại lệ ném phân công di chuyển, và nó thường không!

Ưu điểm của phong cách này:

  1. Bạn không cần phải lo lắng về việc liệu trình biên dịch sẽ gấp đôi khởi tạo các thành viên, vì đó có thể thay đổi trong môi trường khác nhau.
  2. Bạn viết ít mã hơn.
  3. Bạn không cần cập nhật ngay cả khi bạn thêm thành viên bổ sung vào lớp học trong tương lai.
  4. Trình biên dịch thường có thể nội dòng bài tập di chuyển, do đó chi phí của hàm tạo bản sao sẽ tối thiểu.

Tôi nghĩ rằng bài đăng của Howard không trả lời được câu hỏi này. Trong thực tế, các lớp thường không thích việc sao chép, rất nhiều lớp chỉ đơn giản là vô hiệu hoá hàm tạo bản sao và gán bản sao. Nhưng hầu hết các lớp học có thể được di chuyển ngay cả khi chúng không thể sao chép được.

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