Có an toàn khi hoán đổi hai vectơ khác nhau trong C ++, sử dụng phương thức std :: vector :: exchange không?


30

Giả sử bạn có mã sau đây:

#include <iostream>
#include <string>
#include <vector>

int main()
{
    std::vector<std::string> First{"example", "second" , "C++" , "Hello world" };
    std::vector<std::string> Second{"Hello"};

    First.swap(Second);

    for(auto a : Second) std::cout << a << "\n";
    return 0;
}

Hãy tưởng tượng các vectơ không std::string, nhưng các lớp:

std::vector<Widget> WidgetVector;

std::vector<Widget2> Widget2Vector;

Vẫn còn an toàn để trao đổi hai vectơ với std::vector::swapphương thức: WidgetVector.swap(Widget2Vector);hoặc nó sẽ dẫn đến một UB?

Câu trả lời:


20

Nó an toàn vì không có gì được tạo ra trong quá trình trao đổi. Chỉ các thành viên dữ liệu của lớp std::vectorđược hoán đổi.

Hãy xem xét chương trình trình diễn sau đây để làm rõ cách các đối tượng của lớp std::vectorđược hoán đổi.

#include <iostream>
#include <utility>
#include <iterator>
#include <algorithm>
#include <numeric>

class A
{
public:
    explicit A( size_t n ) : ptr( new int[n]() ), n( n )
    {
        std::iota( ptr, ptr + n, 0 );   
    }

    ~A() 
    { 
        delete []ptr; 
    }

    void swap( A & a ) noexcept
    {
        std::swap( ptr, a.ptr );
        std::swap( n, a.n );
    }

    friend std::ostream & operator <<( std::ostream &os, const A &a )
    {
        std::copy( a.ptr, a.ptr + a.n, std::ostream_iterator<int>( os, " " ) );
        return os;
    }

private:    
    int *ptr;
    size_t n;
};

int main() 
{
    A a1( 10 );
    A a2( 5 );

    std::cout << a1 << '\n';
    std::cout << a2 << '\n';

    std::cout << '\n';

    a1.swap( a2 );

    std::cout << a1 << '\n';
    std::cout << a2 << '\n';

    std::cout << '\n';

    return 0;
}

Đầu ra của chương trình là

0 1 2 3 4 5 6 7 8 9 
0 1 2 3 4 

0 1 2 3 4 
0 1 2 3 4 5 6 7 8 9 

Như bạn chỉ thấy các thành viên dữ liệu ptrnđược hoán đổi trong trao đổi chức năng thành viên. Không có tài nguyên bổ sung được sử dụng.

Một cách tiếp cận tương tự được sử dụng trong lớp std::vector.

Ví dụ này

std::vector<Widget> WidgetVector;

std::vector<Widget2> Widget2Vector;

sau đó có các đối tượng của các lớp khác nhau. Việc hoán đổi hàm thành viên được áp dụng cho các vectơ cùng loại.


6
Nhưng trường hợp thực tế của OP , nơi các vectơ được hoán đổi là của các lớp khác nhau ?
Adrian Mole

4
@AdrianMole Trao đổi hàm thành viên nếu được xác định cho loại vectơ đã cho. Nó không được định nghĩa cho các vectơ của các loại khác nhau. Nó không phải là một chức năng thành viên mẫu.
Vlad từ Moscow

"Hoán đổi được áp dụng cho các vectơ cùng loại" Bạn nên thêm "chỉ" giữa "là" và "được áp dụng".
SS Anne

Tất nhiên, người phân bổ nhà nước có thể thay đổi mọi thứ.
Ded repeatator

21

Vâng, điều này là hoàn toàn an toàn để trao đổi các vectơ cùng loại.

Vector dưới mui xe chỉ là một vài con trỏ trỏ đến dữ liệu mà vectơ sử dụng và "kết thúc" của chuỗi. Khi bạn gọi trao đổi, bạn chỉ cần trao đổi các con trỏ giữa các vectơ. Bạn không cần phải lo lắng rằng các vectơ có cùng kích thước vì điều này.

Các vectơ của các loại khác nhau không thể được hoán đổi bằng cách sử dụng swap. Bạn cần thực hiện chức năng của riêng mình để thực hiện chuyển đổi và hoán đổi.


3
Bạn cần xem xét kỹ hơn phần thứ hai của câu hỏi.
Đánh dấu tiền chuộc

@MarkRansom Yep. Mất tích 2. Cập nhật.
NathanOliver

13

Có an toàn khi hoán đổi hai vectơ khác nhau trong C ++, sử dụng phương thức std :: vector :: exchange không?

Đúng. Trao đổi thường có thể được coi là an toàn. Mặt khác, an toàn là chủ quan và tương đối và có thể được xem xét từ các quan điểm khác nhau. Như vậy, không thể đưa ra một câu trả lời thỏa đáng mà không làm tăng câu hỏi với bối cảnh và chọn loại an toàn nào đang được xem xét.

Có an toàn không khi trao đổi hai vectơ với phương thức std :: vector :: exchange: WidgetVector.swap (Widget2Vector); hoặc nó sẽ dẫn đến một UB?

Sẽ không có UB. Vâng, nó vẫn an toàn theo nghĩa là chương trình không được định hình.


7

Các swapchức năng được xác định như sau: void swap( T& a, T& b );. Lưu ý ở đây rằng cả hai ab(và phải ) cùng loại . (Không có chức năng nào được xác định bằng chữ ký này: void swap( T1& a, T2& b )vì nó sẽ vô nghĩa!)

Tương tự, swap()hàm thành viên của std::vectorlớp được định nghĩa như sau:

template<class T1> class vector // Note: simplified from the ACTUAL STL definition
{
//...
public:
    void swap( vector& other );
//...
};

Bây giờ, vì không có định nghĩa 'tương đương' với ghi đè mẫu (xem phần Chuyên môn hóa rõ ràng của các mẫu hàm ) cho tham số hàm (sẽ có dạng template <typename T2> void swap(std::vector<T2>& other):), tham số đó phải là một vectơ cùng loại (mẫu) như lớp 'gọi' (nghĩa là nó cũng phải là một vector<T1>).

Của bạn std::vector<Widget>std::vector<Widget2>hai khác nhau loại, vì vậy các cuộc gọi đến swapsẽ không biên dịch, cho dù bạn cố gắng sử dụng các chức năng thành viên của một trong hai đối tượng (như mã của bạn không), hoặc sử dụng chuyên môn của std::swap()chức năng mà phải mất hai std:vectorđối tượng như tham số.


8
std::vector::swaplà một hàm thành viên, làm thế nào mà có thể là một chuyên môn hóa của một hàm mẫu tự do ???
Aconcagua

1
Kết quả đúng, lý luận sai.
NathanOliver

2
@AdrianMole Mặc dù có tồn tại một chuyên môn hóa std::swap, đó không phải là những gì OP đang sử dụng. Khi bạn làm như vậy First.swap(Second);, bạn gọi std::vector::swapđó là một chức năng khác vớistd::swap
NathanOliver

1
Xin lỗi, xấu của tôi ... Liên kết của bạn đi thẳng vào tài liệu của std :: trao đổi chuyên môn cho các vectơ. Nhưng điều đó khác với chức năng thành viên , cũng tồn tại và được sử dụng trong câu hỏi (chỉ).
Aconcagua

2
Lý do thực sự là tương tự: Thành viên được định nghĩa là void swap(std::vector& other)(nghĩa là std::vector<T>), không phải template <typename U> void swap(std::vector<U>& other)(giả sử T là tham số loại cho chính vectơ).
Aconcagua

4

Bạn không thể hoán đổi các vectơ của hai loại khác nhau nhưng đó là lỗi biên dịch thay vì UB. vector::swapchỉ chấp nhận các vectơ cùng loại và phân bổ.

Không chắc chắn nếu điều này sẽ làm việc, nhưng nếu bạn muốn một vectơ chứa Widget2s được chuyển đổi từWidget s, bạn có thể thử điều này:

std::vector<Widget2> Widget2Vector(
    std::make_move_iterator(WidgetVector.begin()),
    std::make_move_iterator(WidgetVector.end())
);

Widget2sẽ phải được di chuyển xây dựng từ Widget.


0

using std::swap; swap(a, b);a.swap(b); có cùng một ngữ nghĩa chính xác nơi mà cái sau hoạt động; ít nhất là cho bất kỳ loại lành mạnh. Tất cả các loại tiêu chuẩn đều lành mạnh trong vấn đề đó.

Trừ khi bạn sử dụng một công cụ cấp phát thú vị (có nghĩa là trạng thái, không phải lúc nào cũng bằng nhau và không được truyền bá trên trao đổi container, xem std::allocator_traits), hoán đổi haistd::vector s với cùng một đối số mẫu chỉ là một hoán đổi nhàm chán của ba giá trị (cho dung lượng, kích thước và dữ liệu- con trỏ). Và hoán đổi các loại cơ bản, các cuộc đua dữ liệu vắng mặt, là an toàn và không thể ném.

Điều đó thậm chí còn được đảm bảo bởi các tiêu chuẩn. Xem std::vector::swap().

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.