Cách nạp chồng std :: exchange ()


115

std::swap()được sử dụng bởi nhiều thùng chứa tiêu chuẩn (như std::liststd::vector) trong quá trình phân loại và thậm chí gán.

Nhưng việc thực hiện tiêu chuẩn swap()rất chung chung và không hiệu quả đối với các loại tùy chỉnh.

Do đó, hiệu quả có thể đạt được bằng cách quá tải std::swap()với một triển khai cụ thể loại tùy chỉnh. Nhưng làm thế nào bạn có thể thực hiện nó để nó sẽ được sử dụng bởi các thùng chứa std?



Các Swappable trang chuyển đến en.cppreference.com/w/cpp/named_req/Swappable
Andrew Keeton

Câu trả lời:


135

Cách đúng để quá tải trao đổi là viết nó trong cùng một không gian tên như những gì bạn đang trao đổi, để có thể tìm thấy nó thông qua tra cứu phụ thuộc vào đối số (ADL) . Một điều đặc biệt dễ làm là:

class X
{
    // ...
    friend void swap(X& a, X& b)
    {
        using std::swap; // bring in swap for built-in types

        swap(a.base1, b.base1);
        swap(a.base2, b.base2);
        // ...
        swap(a.member1, b.member1);
        swap(a.member2, b.member2);
        // ...
    }
};

11
Trong C ++ 2003, nó không được đánh giá đúng mức. Hầu hết các triển khai đều sử dụng ADL để tìm trao đổi, nhưng không bắt buộc, vì vậy bạn không thể tin tưởng vào nó. Bạn có thể chuyên std :: exchange cho một loại bê tông cụ thể như được hiển thị bởi OP; chỉ không mong đợi rằng chuyên môn hóa sẽ được sử dụng, ví dụ cho các lớp dẫn xuất của loại đó.
Dave Abrahams

15
Tôi sẽ ngạc nhiên khi thấy rằng việc triển khai vẫn không sử dụng ADL để tìm hoán đổi chính xác. Đây là một vấn đề trong ủy ban. Nếu việc triển khai của bạn không sử dụng ADL để tìm trao đổi, hãy báo cáo lỗi.
Howard Hinnant

3
@Sascha: Đầu tiên, tôi đang xác định hàm ở phạm vi không gian tên vì đó là loại định nghĩa duy nhất quan trọng đối với mã chung. Vì int et. al. không / không thể có chức năng thành viên, std :: sort et. al. phải sử dụng chức năng miễn phí; họ thiết lập giao thức. Thứ hai, tôi không biết lý do tại sao bạn phản đối việc có hai triển khai, nhưng hầu hết các lớp sẽ bị sắp xếp không hiệu quả nếu bạn không thể chấp nhận trao đổi không phải là thành viên. Các quy tắc quá tải đảm bảo rằng nếu nhìn thấy cả hai khai báo, thì một quy tắc cụ thể hơn (cái này) sẽ được chọn khi hoán đổi được gọi mà không đủ điều kiện.
Dave Abrahams

5
@ Mozza314: Nó phụ thuộc. A std::sortsử dụng ADL để hoán đổi các phần tử là C ++ 03 không tuân thủ nhưng tuân thủ C ++ 11. Ngoài ra, tại sao -1 câu trả lời dựa trên thực tế là khách hàng có thể sử dụng mã không thành ngữ?
JoeG

4
@cquilguy: Nếu đọc tiêu chuẩn chỉ là một vấn đề đơn giản của việc đọc tiêu chuẩn, bạn sẽ đúng :-). Thật không may, ý định của các tác giả quan trọng. Vì vậy, nếu mục đích ban đầu là ADL có thể hoặc nên được sử dụng, thì nó không được xác định rõ. Nếu không, thì đó chỉ là một sự thay đổi hoàn toàn cũ đối với C ++ 0x, đó là lý do tại sao tôi đã viết ra ở mức tốt nhất.
Dave Abrahams

70

Chú ý Mozza314

Dưới đây là một mô phỏng về các hiệu ứng của một std::algorithmcuộc gọi chung std::swapvà để người dùng cung cấp trao đổi của họ trong không gian tên std. Vì đây là một thử nghiệm, mô phỏng này sử dụng namespace expthay vì namespace std.

// simulate <algorithm>

#include <cstdio>

namespace exp
{

    template <class T>
    void
    swap(T& x, T& y)
    {
        printf("generic exp::swap\n");
        T tmp = x;
        x = y;
        y = tmp;
    }

    template <class T>
    void algorithm(T* begin, T* end)
    {
        if (end-begin >= 2)
            exp::swap(begin[0], begin[1]);
    }

}

// simulate user code which includes <algorithm>

struct A
{
};

namespace exp
{
    void swap(A&, A&)
    {
        printf("exp::swap(A, A)\n");
    }

}

// exercise simulation

int main()
{
    A a[2];
    exp::algorithm(a, a+2);
}

Đối với tôi điều này in ra:

generic exp::swap

Nếu trình biên dịch của bạn in ra một cái gì đó khác nhau thì nó không thực hiện chính xác "tra cứu hai pha" cho các mẫu.

Nếu trình biên dịch của bạn tuân thủ (với bất kỳ C ++ 98/03/11 nào), thì nó sẽ cho cùng một đầu ra mà tôi hiển thị. Và trong trường hợp đó chính xác những gì bạn sợ sẽ xảy ra, sẽ xảy ra. Và việc đặt bạn swapvào không gian tên std( exp) không ngăn điều đó xảy ra.

Dave và tôi đều là thành viên ủy ban và đã làm việc trong lĩnh vực tiêu chuẩn này trong một thập kỷ (và không phải lúc nào cũng đồng ý với nhau). Nhưng vấn đề này đã được giải quyết trong một thời gian dài và cả hai chúng tôi đều đồng ý về cách giải quyết nó. Bỏ qua ý kiến ​​/ câu trả lời của chuyên gia Dave trong lĩnh vực này trong tình trạng nguy hiểm của riêng bạn.

Vấn đề này được đưa ra ánh sáng sau khi C ++ 98 được xuất bản. Bắt đầu khoảng năm 2001 Dave và tôi bắt đầu làm việc trong lĩnh vực này . Và đây là giải pháp hiện đại:

// simulate <algorithm>

#include <cstdio>

namespace exp
{

    template <class T>
    void
    swap(T& x, T& y)
    {
        printf("generic exp::swap\n");
        T tmp = x;
        x = y;
        y = tmp;
    }

    template <class T>
    void algorithm(T* begin, T* end)
    {
        if (end-begin >= 2)
            swap(begin[0], begin[1]);
    }

}

// simulate user code which includes <algorithm>

struct A
{
};

void swap(A&, A&)
{
    printf("swap(A, A)\n");
}

// exercise simulation

int main()
{
    A a[2];
    exp::algorithm(a, a+2);
}

Đầu ra là:

swap(A, A)

Cập nhật

Một quan sát đã được thực hiện rằng:

namespace exp
{    
    template <>
    void swap(A&, A&)
    {
        printf("exp::swap(A, A)\n");
    }

}

làm! Vậy tại sao không sử dụng?

Hãy xem xét trường hợp của bạn Alà một mẫu lớp:

// simulate user code which includes <algorithm>

template <class T>
struct A
{
};

namespace exp
{

    template <class T>
    void swap(A<T>&, A<T>&)
    {
        printf("exp::swap(A, A)\n");
    }

}

// exercise simulation

int main()
{
    A<int> a[2];
    exp::algorithm(a, a+2);
}

Bây giờ nó không hoạt động trở lại. :-(

Vì vậy, bạn có thể đặt swaptrong không gian tên std và làm cho nó hoạt động. Nhưng bạn sẽ cần phải nhớ để đặt swapA's namespace cho trường hợp khi bạn có một mẫu: A<T>. Và vì cả hai trường hợp sẽ hoạt động nếu bạn đặt swaptrong Akhông gian tên, nên việc nhớ (và dạy người khác) sẽ dễ dàng hơn khi làm theo cách đó.


4
Thankyou rất nhiều cho câu trả lời chi tiết. Tôi rõ ràng là ít hiểu biết về điều này và thực sự tự hỏi làm thế nào quá tải và chuyên môn hóa có thể tạo ra hành vi khác nhau. Tuy nhiên, tôi không đề xuất quá tải mà là chuyên môn hóa. Khi tôi đưa template <>vào ví dụ đầu tiên của bạn, tôi nhận được đầu ra exp::swap(A, A)từ gcc. Vì vậy, tại sao không thích chuyên môn hóa?
voltrevo

1
Ồ Điều này thực sự là giác ngộ. Bạn chắc chắn đã thuyết phục tôi. Tôi nghĩ rằng tôi sẽ sửa đổi một chút đề xuất của bạn và sử dụng cú pháp bạn bè trong lớp từ Dave Abrahams (hey tôi có thể sử dụng điều này cho toán tử << quá! :-)), trừ khi bạn cũng có lý do để tránh điều đó (ngoài việc biên dịch riêng biệt). Ngoài ra, về vấn đề này, bạn có nghĩ using std::swaplà một ngoại lệ đối với quy tắc "không bao giờ sử dụng câu lệnh trong tệp tiêu đề" không? Trong thực tế, tại sao không đặt using std::swapbên trong <algorithm>? Tôi cho rằng nó có thể phá vỡ một thiểu số nhỏ mã người. Có thể không ủng hộ và cuối cùng đưa nó vào?
voltrevo

3
cú pháp bạn trong lớp nên ổn. Tôi sẽ cố gắng giới hạn using std::swapphạm vi chức năng trong các tiêu đề của bạn. Vâng, swapgần như là một từ khóa. Nhưng không, nó không hoàn toàn là một từ khóa. Vì vậy, tốt nhất không xuất nó cho tất cả các không gian tên cho đến khi bạn thực sự phải. swaplà giống như operator==. Sự khác biệt lớn nhất là thậm chí không bao giờ nghĩ đến việc gọi operator==bằng cú pháp không gian tên đủ điều kiện (nó sẽ quá xấu xí).
Howard Hinnant

15
@NielKirk: Những gì bạn đang xem là phức tạp chỉ đơn giản là có quá nhiều câu trả lời sai. Không có gì phức tạp về câu trả lời đúng của Dave Abrahams: "Cách đúng để quá tải hoán đổi là viết nó trong cùng một không gian tên như những gì bạn đang trao đổi, để có thể tìm thấy nó thông qua tra cứu phụ thuộc đối số (ADL)."
Howard Hinnant

2
@codeshot: Xin lỗi. Herb đã cố gắng để có được thông điệp này từ năm 1998: gotw.ca/publications/mill02.htm Ông không đề cập đến trao đổi trong bài viết này. Nhưng đây chỉ là một ứng dụng khác của Nguyên tắc Giao diện của Herb.
Howard Hinnant

53

Bạn không được phép (theo tiêu chuẩn C ++) để quá tải std :: exchange, tuy nhiên bạn đặc biệt được phép thêm các chuyên môn mẫu cho các loại của riêng bạn vào không gian tên std. Ví dụ

namespace std
{
    template<>
    void swap(my_type& lhs, my_type& rhs)
    {
       // ... blah
    }
}

sau đó các tập quán trong các thùng chứa tiêu chuẩn (và bất cứ nơi nào khác) sẽ chọn chuyên môn của bạn thay vì sử dụng chung.

Cũng lưu ý rằng việc cung cấp một triển khai trao đổi lớp cơ sở không đủ tốt cho các loại dẫn xuất của bạn. Ví dụ nếu bạn có

class Base
{
    // ... stuff ...
}
class Derived : public Base
{
    // ... stuff ...
}

namespace std
{
    template<>
    void swap(Base& lha, Base& rhs)
    {
       // ...
    }
}

điều này sẽ hoạt động cho các lớp Base, nhưng nếu bạn cố gắng hoán đổi hai đối tượng Derogen thì nó sẽ sử dụng phiên bản chung từ std vì hoán đổi templated là một kết hợp chính xác (và nó tránh được vấn đề chỉ hoán đổi các phần 'cơ sở' của các đối tượng dẫn xuất của bạn ).

LƯU Ý: Tôi đã cập nhật điều này để loại bỏ các bit sai khỏi câu trả lời cuối cùng của tôi. Ôi! (cảm ơn puetzk và j_random_hacker đã chỉ ra nó)


1
Chủ yếu là lời khuyên tốt, nhưng tôi phải -1 vì sự khác biệt tinh tế được ghi nhận bởi puetzk giữa chuyên biệt một mẫu trong không gian tên std (được cho phép theo tiêu chuẩn C ++) và quá tải (không phải là).
j_random_hacker

11
Bị từ chối vì cách chính xác để tùy chỉnh trao đổi là làm như vậy trong không gian tên của riêng bạn (như Dave Abrahams chỉ ra trong một câu trả lời khác).
Howard Hinnant

2
Những lý do của tôi để hạ cấp cũng giống như của Howard
Dave Abrahams

13
@HowardHinnant, Dave Abrahams: Tôi không đồng ý. Trên cơ sở nào bạn tuyên bố thay thế của bạn là cách "chính xác"? Như puetzk trích dẫn từ tiêu chuẩn, điều này được cho phép cụ thể. Mặc dù tôi chưa quen với vấn đề này nhưng tôi thực sự không thích phương pháp mà bạn ủng hộ bởi vì nếu tôi xác định Foo và trao đổi theo cách mà người khác sử dụng mã của tôi có thể sử dụng std :: exchange (a, b) thay vì trao đổi ( a, b) trên Foo, trong đó âm thầm sử dụng phiên bản mặc định không hiệu quả.
voltrevo

5
@ Mozza314: Các hạn chế về không gian và định dạng của khu vực bình luận không cho phép tôi trả lời đầy đủ cho bạn. Vui lòng xem câu trả lời tôi đã thêm có tiêu đề "Chú ý Mozza314".
Howard Hinnant

29

Mặc dù đúng là người ta thường không nên thêm công cụ vào không gian tên std ::, việc thêm các chuyên môn mẫu cho các loại do người dùng xác định được cho phép cụ thể. Quá tải các chức năng là không. Đây là một sự khác biệt tinh tế :-)

17.4.3.1/1 Chương trình C ++ không được xác định để thêm khai báo hoặc định nghĩa vào không gian tên std hoặc không gian tên với không gian tên std trừ khi có quy định khác. Một chương trình có thể thêm các chuyên môn mẫu cho bất kỳ mẫu thư viện tiêu chuẩn nào vào không gian tên std. Sự chuyên môn hóa (hoàn chỉnh hoặc một phần) của thư viện tiêu chuẩn dẫn đến hành vi không xác định trừ khi khai báo phụ thuộc vào tên do người dùng xác định của liên kết bên ngoài và trừ khi chuyên môn mẫu đáp ứng các yêu cầu thư viện chuẩn cho mẫu gốc.

Một chuyên môn của std :: exchange sẽ giống như:

namespace std
{
    template<>
    void swap(myspace::mytype& a, myspace::mytype& b) { ... }
}

Không có mẫu <> bit, nó sẽ là một tình trạng quá tải, không xác định, thay vì chuyên môn hóa, được phép. Cách tiếp cận đề xuất thay đổi không gian tên mặc định của @ Wilka có thể hoạt động với mã người dùng (do tra cứu Koenig thích phiên bản không có tên không gian) nhưng thực tế nó không được bảo đảm và thực tế không nên sử dụng (việc triển khai STL phải sử dụng đầy đủ -đánh giá tiêu chuẩn :: trao đổi).

Có một chủ đề trên comp.lang.c ++. Được kiểm duyệt với một đoạn dài của chủ đề. Hầu hết trong số đó là về chuyên môn hóa một phần, mặc dù (hiện tại không có cách nào tốt để làm).


7
Một lý do sai lầm khi sử dụng chuyên môn hóa mẫu chức năng cho việc này (hoặc bất cứ điều gì): nó tương tác theo cách xấu với tình trạng quá tải, trong đó có nhiều thứ để trao đổi. Ví dụ: nếu bạn chuyên std :: exchange cho std :: vector <mytype> &, chuyên môn của bạn sẽ không được chọn qua trao đổi cụ thể theo vectơ tiêu chuẩn, vì các chuyên môn không được xem xét trong quá trình giải quyết quá tải.
Dave Abrahams

4
Đây cũng là những gì Meyers khuyên dùng trong C ++ 3ed hiệu quả (Mục 25, trang 106-112).
jww

2
@DaveAbrahams: Nếu bạn chuyên (không có đối số mẫu rõ ràng), một phần đặt hàng sẽ gây ra nó để trở thành một chuyên môn hóa của các vectorphiên bản và nó sẽ được sử dụng .
Davis Herring

1
@DavisHerring thực sự, không, khi bạn thực hiện việc đặt hàng một phần không có vai trò. Vấn đề không phải là bạn không thể gọi nó cả; đó là những gì xảy ra với sự có mặt của quá tải hoán đổi rõ ràng ít cụ thể hơn: Wandbox.org/permlink/nck8BkG0WPlRtavV
Dave Abrahams

2
@DaveAbrahams: Thứ tự một phần là chọn mẫu hàm để chuyên môn hóa khi chuyên môn rõ ràng khớp với nhiều hơn một. Quá ::swaptải mà bạn đã thêm là chuyên biệt hơn so với std::swapquá tải vector, do đó, nó thực hiện cuộc gọi và không có chuyên môn nào sau này có liên quan. Tôi không chắc đó là một vấn đề thực tế như thế nào (nhưng tôi cũng không cho rằng đây là một ý tưởng hay!).
Davis Herring
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.