2013-01-18 38 views
19

我今天讀一些有趣的事情提到「標準」的方式來呼籲用戶提供的類型(如模板參數提供)互換是...你應該在std命名空間中重載swap嗎?

using std::swap; 
swap(something, soemthingelse); 

這樣做的原因是使用的參數依賴的樣子要麼在用戶名空間中使用swap函數,要麼在std名稱空間中使用swap。這對我提出了一個感興趣的問題。當我爲我的一個類重載std::swap時,我實際上已經在std命名空間中定義了它...... namespace std { void swap(/*...*/){/*...*/} }。這種做法錯誤?我應該在std或我自己的命名空間(以及爲什麼)中定義自己的swap

+0

我想你總是可以做一個明確的專業化,它是*允許的...... –

+0

@KerrekSB仍然問題在於是否應該禁止專業化,以支持啓用ADL的自有名稱空間函數。 –

+0

如果你們倆都這樣做,你們難道不明白嗎? –

回答

18

你就錯了:)

17.6.2.4.1 [namespace.std]

  1. 一個C的行爲++程序是不確定的,如果它增加了聲明或者,除非另有規定定義,命名空間std或命名空間的命名空間std內。程序可以添加一個模板專業化爲任何標準庫模板命名空間std只有在聲明依賴於用戶定義類型和專業化符合原始模板的標準庫要求,並沒有明確禁止。

這很清楚地說,你可以不添加過載到命名空間std。您可以專門std::swap<MyType>你的類型,但如果你的類型是一個模板,你需要一個局部特殊化,std::swap<MyContainer<T>>你不能偏特函數模板,所以這是行不通的,所以它不是一個好方法一般來說。

C++ 11還定義一類的要求是可交換其中包括:

17.6.3.2 [swappable.requirements]

  1. ...
  2. ...
  3. 上下文中swap(t, u)swap(u, t)被評估應確保通過重載解析(13.3)上的候選集合,其中包括選擇命名爲「交換」的二進制非成員函數:
  • 中定義的兩個交換函數模板<utility>(20.2)和
  • 通過參數相關的查找(3.4.2)中產生的查找集。

因此呼籲swap兩個熱插拔類型的對象應該能夠找到std::swap,應該能夠通過ADL找到其他重載。調用它不合格(和沒有明確的模板參數列表)保證ADL發生,包括<utility>並添加使用聲明爲std::swap確保標準的重載可以找到。因此,按照您在問題中展示的方式來滿足這些要求。

這很清楚地定義了標準庫所要求的標準所使用的意義上的可交換的含義,例如,通過<algorithm>中的函數。

如果你把swap重載到你的類型的命名空間中,那麼它們可以被ADL找到。無論如何,這是正確的做法,與您的類型相關的函數與您的類型屬於相同的名稱空間,請參閱Sutter和Alexandrescu的中的Item 57以瞭解有關該主題的更多詳細信息。

總之,你一直在做錯了。你讀的是正確的。做using std::swap和依靠ADL總是工作(模板和非模板),並避免未定義的行爲。好極了。

N.B. C++ 03標準對用戶定義的類型應該如何交換不太清楚。有關此區域的一些歷史記錄,請參閱N1691 2.2,其中定義了術語定製點並顯示了在API中定義它們的不同方法。 C++ 11中用於交換類型的協議使用這些方法之一,現在明確而明確地祝福您爲您的類型提供交換功能的「正確方法」。其他庫中的其他自定義點可以使用其他方法,但可以使用C++ 11進行交換,這意味着可以使用using std::swap;並依賴於ADL。

6

爲您自己的類型提供標準模板的特化是合法的,而且這些模板必須位於std名稱空間內。所以這兩種方法都是合法的C++。

這就是說,推薦的方法是提供swap免費函數在與您自己的類型相同的名稱空間,並讓ADL從那裏獲取您的超載。


編輯:經過一些評論我重讀的問題,並注意到它提到重載在std命名空間。它是非法std命名空間中提供過載,只允許專業被允許。

+0

他說他將重載添加到'namespace std',而不是專門化。所以這是非法的。 –

+0

@JonathanWakely在我的情況下,我必須重載,因爲我交換的參數是模板類型。那裏看來是沒有語法專攻(我想語法像這樣也許...'模板<> 模板 無效掉,事情>(事及左,件事&右);') – David

+1

@戴夫噢,在這種情況下,你沒有機會提供適當的'std :: swap'專精,並且不會利用基於ADL的方法(儘管在實踐中'std :: swap'-overload won'不會傷害任何人,即使技術上不允許/ UB,但它使得已經不那麼慣用的解決方案更成問題)。 –

3

首先,您不允許將東西添加到std(只是過載),所以重載std::swap首先是不好的做法。你可以做的是專門化現有的模板。所以,你應該專注,而不是過載:

namespace std 
{ 
    template<> void swap<MyType>(...) {...} 
} 

但仍然問題表示如果自己的命名空間的版本要優於std -specialization。 idomatic推薦的方法是在你自己的命名空間中提供一個免費的swap,並讓ADL解決它,如David已經暗示。問題是,如果某人(你的庫的客戶端)不熟悉慣用的C++,並且只是明確地調用std::swap(...)到處都是,那麼最終可能會導致次優交換行爲。這就是爲什麼我自己通常採取兩種安全方式(自己的命名空間功能+ std-專業化),即使這對我來說總是不好。

幸好C++ 11簡化了一些事情,因爲有效的可移動類型(通常也是高效的可交換類型)可以使用默認的std::swap(它使用移動語義)相當有效地進行交換。因此,通過自己的swap函數可能使用默認的變得不是一個缺點(並且您甚至可以考慮不打算提供一個)。所以如果你有C++ 11,最好的方法是不用打擾擴展std,而只是在你的類型的命名空間中定義你自己的swap,而在C++ 03中你可能想要安全的一面即使不那麼習慣,也可以使用專業化的std::swap

+0

這是未定義的行爲,將重載添加到'namespace std'' –

+0

*「我認爲重載是不好的做法」* - 您是否指的是一般的重載?或者只是在命名空間std中重載? –

+0

@BenjaminLindley當然,只有'std'。但好點,將編輯。 –

0

理想的做法是僅當您認爲std :: swap對您的類型效率低下時才提供公共交換成員函數。 如果您提供交換成員函數,建議您還提供一個調用此成員函數的非成員交換。這使得其他人可以更輕鬆地調用更高效的模板特定版本。

相關問題