Làm thế nào để thực thi ngữ nghĩa di chuyển khi một vectơ lớn lên?


92

Tôi có một std::vectortrong các đối tượng của 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 sao chép và các hàm tạo di chuyển được xác định.

std::vector<A>  myvec;

Nếu tôi lấp đầy vectơ với Acác đối tượng (sử dụng ví dụ myvec.push_back(a)), vectơ sẽ tăng kích thước, sử dụng hàm tạo bản sao A( const A&)để khởi tạo các bản sao mới của các phần tử trong vectơ.

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


5
Bạn có thể, bằng cách sử dụng triển khai vectơ nhận biết chuyển động.
K-ball

2
Bạn có thể vui lòng cho biết cụ thể hơn một chút làm thế nào để đạt được điều này?
Bertwim van Beest

1
Bạn chỉ cần sử dụng triển khai vectơ nhận biết chuyển động. Có vẻ như việc triển khai thư viện tiêu chuẩn của bạn (nó là btw?) Không nhận biết được di chuyển. Bạn có thể thử với các vùng chứa nhận biết chuyển động từ Boost.
K-ball

1
Vâng, tôi sử dụng gcc 4.5.1, có nhận thức về chuyển động.
Bertwim van Beest

Trong mã của tôi, nó đã làm việc để đặt hàm tạo bản sao là riêng tư, mặc dù hàm tạo di chuyển không có "noexcept" rõ ràng.
Arne

Câu trả lời:


126

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

Đây là cách khai báo và triển khai một hằng số di chuyển đượ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 phương thức khởi tạo không phải noexcept, std::vectorkhông thể sử dụng nó, vì nó không thể đảm bảo các bảo đảm ngoại lệ theo yêu cầu của tiêu chuẩn.

Để biết thêm về những gì được nói trong tiêu chuẩn, hãy đọc ngữ nghĩa và ngoại lệ của C ++ Move

Tín dụng cho Bo, người ám chỉ rằng nó có thể phải làm với các trường hợp ngoại lệ. Cũng nên xem xét lời khuyên của Kerrek SB và sử dụng emplace_backkhi có thể. Nó thể nhanh hơn (nhưng thường thì không), nó có thể rõ ràng 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 hàm tạo 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 rõ ràng điều đó, hãy viết

A(A && rhs) = default;

Làm điều đó, bạn sẽ nhận được noexcept khi có thể: Phương thức khởi tạo Move mặc định có được định nghĩa là noexcept không?

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


Không quan tâm, làm thế nào impl "biết" liệu value_typector 's di chuyển là noexcept? Có lẽ ngôn ngữ hạn chế bộ ứng cử viên cuộc gọi hàm khi phạm vi gọi cũng là một noexcepthàm?
Lightness Races in Orbit

1
@LightnessRacesinOrbit Tôi cho rằng nó chỉ đang làm một cái gì đó chẳng hạn như en.cppreference.com/w/cpp/types/is_move_constructible . Chỉ có thể có một phương thức khởi tạo di chuyển nên nó phải được khai báo xác định rõ ràng.
Johan Lundberg

@LightnessRacesinOrbit, tôi đã biết rằng không có cách nào (tiêu chuẩn / hữu ích) để thực sự biết liệu có một hàm tạo noexceptchuyển động hay không. is_nothrow_move_constructiblesẽ đúng nếu có một hàm tạo nothrowbản sao. Tôi không biết về bất kỳ trường hợp thực tế nào về các nothrowtrình tạo bản sao đắt tiền nên không rõ rằng nó thực sự quan trọng.
Johan Lundberg

Không hiệu quả với tôi. Hàm hủy, hàm tạo di chuyển và hàm gán di chuyển của tôi đều được đánh dấu noexcepttrong cả tiêu đề và quá trình triển khai, và khi tôi thực hiện một push_back (std:; move), nó vẫn gọi hàm tạo bản sao. Tôi đang xé tóc ra đây.
AlastairG

1
@Johan Tôi đã tìm thấy vấn đề. Tôi đã sử dụng std::move()sai push_back()cuộc gọi. Một trong những thời điểm khi bạn đang tìm kiếm một vấn đề quá khó khăn mà bạn không nhìn thấy lỗi rõ ràng ngay trước mặt mình. Và sau đó là giờ ăn trưa và tôi quên xóa bình luận của mình.
AlastairG

17

Điều thú vị là, vectơ của gcc 4.7.2 chỉ sử dụng hàm tạo di chuyển nếu cả hàm tạo di chuyển và hàm hủy đều như vậy 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();
}

Điều này cho kết quả mong đợi:

move
move
move

Tuy nhiên, khi tôi xóa noexceptkhỏi ~foo(), kết quả sẽ khác:

copy
copy
copy

Tôi đoán điều này cũng trả lời câu hỏi này .


Đối với tôi, có vẻ như các câu trả lời khác chỉ nói về hàm tạo chuyển động, không phải về hàm hủy phải là không có ngoại lệ.
Nikola Benes

Chà, lẽ ra là vậy, nhưng hóa ra, trong gcc 4.7.2 thì không. Vì vậy, trên thực tế, vấn đề này là cụ thể đối với gcc. Tuy nhiên, nó sẽ được sửa trong gcc 4.8.0. Xem câu hỏi liên quan đến stackoverflow .
Nikola Benes

-1

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

Có nhiều quy tắc std::vectorKHÔNG PHẢI sử dụng hàm tạo di chuyển khi phân bổ lại, nhưng không có gì về nơi PHẢI SỬ DỤNG nó.

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;   
};

Trực tiếp

hoặc là

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 {};   
};

Mã trực tiếp

TLớp của bạn phải có noexcepttoán tử khởi tạo / gán di chuyển và hàm noexcepthủy. Nếu không, bạn sẽ gặp lỗi biên dịch.

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

1
Không cần thiết phải xóa phương thức khởi tạo sao chép. Nếu phương thức khởi tạo move là noexcept, nó sẽ được sử dụng.
balki

@balki CÓ THỂ được sử dụng. Tiêu chuẩn không YÊU CẦU điều này ngay bây giờ. Đây là thảo luận groups.google.com/a/isocpp.org/forum/…
tower120
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.