2016-09-26 14 views
5

Khi đọc về C++ 11, tôi có cảm giác rằng khi sử dụng các thùng chứa tiêu chuẩn (như std::vector) với kiểu dữ liệu người dùng được khuyến khích cung cấp các hoạt động di chuyển noexcept, nếu có, bởi vì sau đó chỉ có các container sẽ thực sự di chuyển dữ liệu xung quanh thay vì sao chép.Có lợi ích gì từ các hoạt động di chuyển không được chấp nhận khi sử dụng các vùng chứa trong C++ 17 không?

Khi thử điều đó hôm nay, tôi thấy không có sự khác biệt với -std=c++1y (cho C++ 14) và thậm chí là g ++ - 4,8. Có lẽ tôi đã bỏ lỡ một bản cập nhật trong spec, có thể ví dụ của tôi là sai.

tôi so ba cấu trúc dữ liệu cần được di chuyển với sự khác biệt

  • di chuyển theo mặc định bằng cách làm theo "Quy tắc của Zero"
  • di chuyển bằng cách cung cấp di chuyển-ops mà khôngnoexcept
  • di chuyển bằng cách cung cấp di chuyển-ops vớinoexcept

Khung:

#include <string> 
#include <vector> 
#include <chrono> 
#include <iostream> // cout 

using std::vector; using std::cout;  
using namespace std::chrono; 

long long millisSeit(steady_clock::time_point start) { 
    return duration_cast<milliseconds>(steady_clock::now()-start).count(); 
} 

namespace {  
constexpr size_t ITERATIONS = 1000*1000; 

template<typename ELEM> 
void timeStuff(std::string name) { 
    cout << name << "..."; 
    auto start = steady_clock::now(); 
    std::vector<ELEM> data{}; 
    for(size_t idx=0; idx<ITERATIONS; ++idx) { 
     data.emplace_back(idx % 1719); 
    } 
    cout << " " << millisSeit(start) << " ms" << std::endl; 
} 
} 

Với ba kiểu dữ liệu của tôi:

struct RuleOfZeroVector { 
    std::vector<int> val_; 
    RuleOfZeroVector(int val) : val_(val, val) {} 
}; 
struct MoveExceptVector { 
    std::vector<int> val_; 
    MoveExceptVector(int val) : val_(val, val) {} 
    MoveExceptVector(MoveExceptVector&& o) /*noexcept*/ : val_{} { swap(val_, o.val_); } 
    MoveExceptVector& operator=(MoveExceptVector&& o) /*noexcept*/ { swap(val_, o.val_); return *this; } 
}; 
struct MoveNoExceptVector { 
    std::vector<int> val_; 
    MoveNoExceptVector(int val) : val_(val, val) {} 
    MoveNoExceptVector(MoveNoExceptVector&& o) noexcept : val_{} { swap(val_, o.val_); } 
    MoveNoExceptVector& operator=(MoveNoExceptVector&& o) noexcept { swap(val_, o.val_); return *this; } 
}; 

Và thực hiện các timings:

int main() { 
    timeStuff<RuleOfZeroVector>("RuleOfZeroVector"); 
    timeStuff<MoveExceptVector>("MoveExceptVector"); 
    timeStuff<MoveNoExceptVector>("MoveNoExceptVector"); 
} 

Với kết quả:

RuleOfZeroVector... 2461 ms 
MoveExceptVector... 2472 ms 
MoveNoExceptVector... 2468 ms 

Như bạn có thể thấy, không có gì khác biệt ce.

Tôi mong đợi MoveExceptVector chậm hơn nhiều so với hai phương pháp kia, vì tôi giả định vector sẽ sử dụng nhiều bản sao khi cấu trúc dữ liệu nội bộ phát triển. Sai rồi?

+2

Thêm toán tử ctor/assignment vào 'MoveExceptVector' và bạn sẽ thấy sự khác biệt. – ildjarn

+6

Không cần 'vector :: clear()' để di chuyển dữ liệu xung quanh. Nó đơn giản chạy hàm hủy của mỗi phần tử. 'vector :: push_back' là người thụ hưởng chính của công trình di chuyển' noexcept'. –

+1

"g ++ - 4,8 thậm chí". Loạt 4.8 đã được phát hành vào năm 2013. Đó là eons trước đây. –

Trả lời

3

Nguyên tắc có liên quan mà vector sử dụng để chèn một yếu tố duy nhất ở cuối là:

  • Nếu di chuyển kiểu không thể ném (ví dụ: hoạt động di chuyển được đánh dấu noexcept) sau đó di chuyển. Trong trường hợp này, chèn vào cuối của một vectơ cung cấp sự đảm bảo an toàn ngoại lệ mạnh mẽ.
  • Nếu di chuyển loại có thể ném và có thể sao chép được, sau đó sao chép nó. Điều này đảm bảo rằng nếu một ngoại lệ được ném, chúng tôi đã không di chuyển một số đối tượng ban đầu, mà sẽ để lại đối tượng ban đầu trong trạng thái không xác định. Trong trường hợp này, bạn cũng nhận được sự bảo đảm ngoại lệ mạnh mẽ.
  • Nếu không, vì không thể sao chép được, bạn không có lựa chọn, bạn phải di chuyển. Nếu một ngoại lệ được ném một số đối tượng nguồn có thể đã được di chuyển từ, và một số sẽ không được. Trong trường hợp này, bạn chỉ nhận được bảo đảm ngoại lệ cơ bản.

Trong ví dụ của bạn, các đối tượng không thể sao chép được, bởi vì các nhà xây dựng di chuyển do người dùng cung cấp và các toán tử chuyển nhượng gây ra hàm tạo bản sao ngầm định được xóa. Vì vậy, vector không có sự lựa chọn. Nó phải sử dụng các hoạt động di chuyển mặc dù chúng không phải là noexcept.

Nếu bạn tạo các loại có thể sao chép, bạn sẽ thấy sự khác biệt, với MoveExceptVector chậm hơn nhiều, vì bây giờ vector có sự lựa chọn giữa thực hiện các thao tác có thể ném hoặc sao chép.

struct MoveExceptVector { 
    std::vector<int> val_; 
    MoveExceptVector(int val) : val_(val, val) {} 
    MoveExceptVector(MoveExceptVector&& o) /*noexcept*/ : val_{} { swap(val_, o.val_); } 
    MoveExceptVector& operator=(MoveExceptVector&& o) /*noexcept*/ { swap(val_, o.val_); return *this; } 
    // ADDED: 
    MoveExceptVector(const MoveExceptVector&) = default; 
}; 

Bạn không thể mong đợi chọn sao chép nếu đối tượng không thể sao chép được.

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