web-dev-qa-db-ja.com

なぜstd :: swapの専門化が非常に多いのですか?

std::swap 、私は多くの専門分野を見ています。
すべてのSTLコンテナ、および他の多くのstdファシリティが特殊なスワップを持っているようです。
テンプレートの助けを借りて、これらすべての専門分野は必要ないと思いましたか?

例えば、
自分でpairを作成すると、テンプレートバージョンで正しく動作します。

template<class T1,class T2> 
struct my_pair{
    T1 t1;
    T2 t2;
};

int main() {
    my_pair<int,char> x{1,'a'};
    my_pair<int,char> y{2,'b'};
    std::swap(x,y);
} 

それで、特殊化std::pair

template< class T1, class T2 >
void swap( pair<T1,T2>& lhs, pair<T1,T2>& rhs );

また、カスタムクラス用に独自の専門分野を作成する必要があるのか​​どうか疑問に思っています。
または単にテンプレートバージョンに依存します。

41
Trevor Hickey

それで、std :: pairの専門化から何が得られますか?

パフォーマンス。一般的なスワップは通常は十分ですが(C++ 11以降)、まれに最適です(std::pair、および他のほとんどのデータ構造用)。

また、カスタムクラス用に独自の専門分野を作成する必要があるのか​​、単にテンプレートバージョンに依存するのか疑問に思っています。

デフォルトでテンプレートに依存することをお勧めしますが、プロファイリングがそれがボトルネックであることを示している場合、おそらく改善の余地があることを知っています。時期尚早の最適化とそのすべて...

38
eerorika

std::swapは、以下のコードの行に沿って実装されます。

template<typename T> void swap(T& t1, T& t2) {
    T temp = std::move(t1); 
    t1 = std::move(t2);
    t2 = std::move(temp);
}

(詳細については "標準ライブラリはstd :: swapをどのように実装するか? を参照してください。)

それで、特殊化std::pair

std::swapは、次のように特化できますlibc++

void swap(pair& p) noexcept(is_nothrow_swappable<first_type>{} &&
                            is_nothrow_swappable<second_type>{})
{
    using std::swap;
    swap(first,  p.first);
    swap(second, p.second);
}

ご覧のとおり、swapは、ADLを使用してペアの要素で直接呼び出されます。これにより、swapおよびfirstsecondのカスタマイズされた潜在的に高速な実装を使用できます(これらの実装は知識を活用できます)要素の内部構造のパフォーマンス向上のため)

"How does using std::swap ADLを有効にしますか? " 詳細については、)

34
Vittorio Romeo

おそらくこれは、pairに含まれるタイプがvectorのように、スワップは安価ですがコピーは高価な場合のパフォーマンス上の理由によるものです。一時オブジェクトを使用してコピーを行う代わりに、firstおよびsecondでスワップを呼び出すことができるため、プログラムのパフォーマンスが大幅に向上する場合があります。

12
Mark B

2つのペアを交換する最も効率的な方法は、2つのベクトルを交換する最も効率的な方法と同じではありません。 2つのタイプには、異なる実装、異なるメンバー変数、および異なるメンバー関数があります。

この方法で2つのオブジェクトを「スワップ」する一般的な方法はありません。

つまり、コピー可能なタイプの場合、これを行うことができます:

T tmp = a;
a = b;
b = tmp;

しかし、それは恐ろしいことです。

可動タイプの場合、std::moveおよびコピーを防止しますが、実際には有用な移動セマンティクスを使用するために、次のレイヤーで「スワップ」セマンティクスが必要ですstillある時点で、専門化する必要があります。

その理由は、パフォーマンス、特にC++ 11以前です。

「ベクター」タイプのようなものを考えてください。ベクターには、サイズ、容量、および実際のデータへのポインターの3つのフィールドがあります。コピーコンストラクターとコピーの割り当てが実際のデータをコピーします。 C++ 11バージョンには、ポインターを盗み、ソースオブジェクトのポインターをnullに設定する移動コンストラクターと移動割り当てもあります。

専用のベクタースワップ実装では、フィールドを単純にスワップできます。

コピーコンストラクタ、コピーの割り当て、およびデストラクタに基づく汎用スワップの実装では、データのコピーと動的なメモリの割り当て/割り当て解除が行われます。

移動コンストラクター、移動割り当て、およびデストラクターに基づく汎用スワップ実装は、データのコピーやメモリーの割り当てを回避しますが、オプティマイザーが最適化できる場合とできない場合がある冗長なヌルとヌルチェックを残します。


では、なぜ「ペア」専用のスワップ実装があるのでしょうか? intとcharのペアの場合、必要はありません。これらは単純な古いデータ型であるため、汎用スワップは問題ありません。

しかし、VectorとStringのペアがある場合はどうでしょうか?これらのタイプにはスペシャリストのスワップ操作を使用したいので、コンポーネントタイプをスワップすることでそれを処理するペアタイプでスワップ操作が必要です。

4
plugwash

あなたの型が汎用の_std::swap_関数よりもスローしない、または高速なスワップ実装を提供できる場合、ルールがあります(Herb SutterのExceptional C++またはScott MeyerのEffective C++シリーズのいずれかに由来すると思います)。メンバー関数void swap(T &other)として行う必要があります。

理論的には、汎用のstd::swap()関数はテンプレートマジックを使用してメンバースワップの存在を検出し、それを呼び出す代わりにそれを呼び出すことができます

_T tmp = std::move(lhs);
lhs = std::move(rhs);
rhs = std::move(tmp);
_

しかし、誰もそのことを考えていないようですので、人々は(潜在的に高速な)メンバースワップを呼び出すために、無料のswapのオーバーロードを追加する傾向があります。

1