2011-11-03 26 views
62

Tôi có một số std::vector đối tượng thuộc một lớp nhất định A. Lớp này không tầm thường và có các hàm tạo bản sao di chuyển các hàm tạo đã xác định.Làm thế nào để thực thi ngữ nghĩa di chuyển khi một véc tơ tăng lên?

std::vector<A> myvec; 

Nếu tôi lấp đầy các vector với A đối tượng (sử dụng ví dụ myvec.push_back(a)), vector sẽ phát triển về kích thước, bằng cách sử dụng bản sao constructor A(const A&) để nhanh chóng bản sao mới của các yếu tố trong vector.

Tôi có thể thực thi bằng cách nào đó rằng hàm tạo di chuyển của lớp A được sử dụng thay thế?

+4

Bạn có thể, bằng cách sử dụng triển khai thực hiện vector di chuyển. –

+1

Bạn có thể vui lòng biết thêm một chút về cách thực hiện điều này không? –

+1

Bạn chỉ cần sử dụng triển khai thực hiện vector di chuyển. Có vẻ như việc thực hiện thư viện chuẩn của bạn (tức là btw?) Không di chuyển. Bạn có thể thử với các thùng chứa nhận biết di chuyển từ Boost. –

Trả lời

84

Bạn cần thông báo cho C++ (cụ thể là std::vector) rằng hàm khởi tạo và hủy của bạn không ném, sử dụng noexcept. Sau đó, hàm tạo di chuyển sẽ được gọi khi vector phát triển.

Đây là cách khai báo và thực hiện một constuctor di chuyển mà được tôn trọng bởi std::vector:

A(A && rhs) noexcept { 
    std::cout << "i am the move constr" <<std::endl; 
    ... some code doing the move ... 
    m_value=std::move(rhs.m_value) ; // etc... 
} 

Nếu các nhà xây dựng không phải là noexcept, std::vector không thể sử dụng nó, kể từ đó nó không thể đảm bảo sự ngoại lệ đảm bảo theo yêu cầu của tiêu chuẩn.

Để biết thêm về những gì đã nói trong tiêu chuẩn, đọc C++ Move semantics and Exceptions

tín dụng đối với Bo người ám chỉ rằng nó có thể có để làm với trường hợp ngoại lệ. Cũng xem xét lời khuyên của Kerrek SB và sử dụng emplace_back khi có thể. Nó có thể được nhanh hơn (nhưng thường không phải là), nó có thể rõ ràng hơn và nhỏ gọn hơn, nhưng cũng có một số cạm bẫy (đặc biệt là với các nhà thầu không rõ ràng).

Chỉnh sửa, thường mặc định là những gì bạn muốn: di chuyển mọi thứ có thể di chuyển, sao chép phần còn lại. Để yêu cầu một cách rõ ràng cho điều đó, hãy viết

A(A && rhs) = default; 

Làm như vậy, bạn sẽ nhận được noexcept khi có thể: Is the default Move constructor defined as noexcept?

Lưu ý rằng phiên bản đầu tiên của Visual Studio 2015 và cũ không hỗ trợ đó, mặc dù nó hỗ trợ chuyển ngữ nghĩa .

+11

Lưu ý: 'throw()' không được dùng nữa, thay vào đó hãy sử dụng 'noexcept'. –

+0

@MatthieuM. À, vâng - đã sửa nó ngay bây giờ. Vâng phát hiện. –

+0

Không quan tâm, làm thế nào _does_ impl "biết" liệu ctor di chuyển 'value_type' là' noexcept'? Có lẽ ngôn ngữ hạn chế các ứng cử viên gọi chức năng thiết lập khi phạm vi gọi điện thoại cũng là một chức năng 'noexcept'? –

14

Điều thú vị là vector của gcc 4.7.2 chỉ sử dụng công cụ chuyển động di chuyển nếu cả hàm khởi tạo di chuyển và hàm hủy là noexcept. Một ví dụ đơn giản:

struct foo { 
    foo() {} 
    foo(const foo &) noexcept { std::cout << "copy\n"; } 
    foo(foo &&) noexcept { std::cout << "move\n"; } 
    ~foo() noexcept {} 
}; 

int main() { 
    std::vector<foo> v; 
    for (int i = 0; i < 3; ++i) v.emplace_back(); 
} 

này kết quả đầu ra dự kiến:

move 
move 
move 

Tuy nhiên, khi tôi loại bỏ noexcept từ ~foo(), kết quả là khác nhau:

copy 
copy 
copy 

Tôi đoán đây cũng trả lời this question .

+0

Dường như với tôi rằng các câu trả lời khác chỉ nói về các nhà xây dựng di chuyển, không phải về _destructor_ phải được noexcept. –

+2

Điểm tốt, nhưng phá hủy là không nhận diện theo mặc định. –

+0

Vâng, nó sẽ được, nhưng khi nó quay ra, trong gcc 4.7.2 nó đã không được. Vì vậy, vấn đề này, trên thực tế, cụ thể đối với gcc. Tuy nhiên, nó sẽ được sửa trong gcc 4.8.0. Xem [câu hỏi stackoverflow liên quan] (http://stackoverflow.com/questions/15721544/destructors-and-noexcept). –

0

Dường như, đó là cách duy nhất (cho C++ 17 và đầu), để thực thi std::vector sử dụng ngữ nghĩa di chuyển trên phân bổ lại là xóa bản sao của hàm tạo :).Bằng cách này, nó sẽ sử dụng các nhà xây dựng di chuyển của bạn hoặc chết cố gắng, tại thời gian biên dịch :).

Có nhiều quy tắc trong đó std::vector PHẢI KHÔNG sử dụng hàm tạo di chuyển trên phân bổ lại, nhưng không có gì về vị trí của nó PHẢI SỬ DỤNG.

template<class T> 
class move_only : public T{ 
public: 
    move_only(){} 
    move_only(const move_only&) = delete; 
    move_only(move_only&&) noexcept {}; 
    ~move_only() noexcept {}; 

    using T::T; 
}; 

Live

hoặc

template<class T> 
struct move_only{ 
    T value; 

    template<class Arg, class ...Args, typename = std::enable_if_t< 
      !std::is_same_v<move_only<T>&&, Arg > 
      && !std::is_same_v<const move_only<T>&, Arg > 
    >> 
    move_only(Arg&& arg, Args&&... args) 
     :value(std::forward<Arg>(arg), std::forward<Args>(args)...) 
    {} 

    move_only(){} 
    move_only(const move_only&) = delete; 
    move_only(move_only&& other) noexcept : value(std::move(other.value)) {};  
    ~move_only() noexcept {}; 
}; 

Live code

lớp T của bạn phải có noexcept di chuyển hành constructor/assigment và noexcept destructor. Nếu không, bạn sẽ nhận được lỗi biên dịch.

std::vector<move_only<MyClass>> vec; 
+0

Không cần thiết phải xóa bản sao. Nếu constructor di chuyển là noexcept, nó sẽ được sử dụng. – balki

+0

@balki CÓ THỂ được sử dụng. Tiêu chuẩn không YÊU CẦU này ngay bây giờ. Đây là cuộc thảo luận https://groups.google.com/a/isocpp.org/forum/?utm_medium=email&utm_source=footer#!msg/std-proposals/j5URs5ZY3GI/AanXG977CAAJ – tower120

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