34

最近、独自の機能を提供する方法について多くの 質問が 出てきますswap。C ++ 11では、セマンティクスをstd::swap使用std::moveおよび移動して、指定された値を可能な限り高速に交換します。もちろん、これは、ムーブコンストラクターとムーブ代入演算子(または値渡しを使用する演算子)を指定した場合にのみ機能します。

さて、それを踏まえて、実際swapにC ++ 11で独自の関数を作成する必要がありますか?私は動かせないタイプしか考えられませんでしたが、繰り返しになりますが、カスタムswapは通常、ある種の「ポインター交換」(別名移動)を介して機能します。たぶん、特定の参照変数を使用しますか?うーん...

4

5 に答える 5

24

それは判断の問題です。私は通常、std::swapコードのプロトタイピングの仕事をさせますが、リリースコードの場合はカスタムスワップを作成します。私は通常、1つの移動構築+2つの移動割り当て+1つのリソースレス破壊の約2倍の速度のカスタムスワップを作成できます。std::swapただし、実際にパフォーマンスの問題であることが判明するまで待ってから、面倒な作業を行うことをお勧めします。

Alf P. Steinbachの更新:

20.2.2 [utility.swap]std::swap(T&, T&)は、noexcept次と同等のものを指定します。

template <class T>
void
swap(T& a, T& b) noexcept
                 (
                    is_nothrow_move_constructible<T>::value &&
                    is_nothrow_move_assignable<T>::value
                 );

つまり、移動操作Tがであるnoexcept場合、std::swapオンTnoexceptです。

この仕様では、メンバーの移動は必要ありません。右辺値からの構築と割り当てが存在することだけが必要であり、存在する場合noexcept、スワップはになりますnoexcept。例えば:

class A
{
public:
    A(const A&) noexcept;
    A& operator=(const A&) noexcept;
};

std::swap<A>移動メンバーがなくても例外ではありません。

于 2011-06-20T19:40:59.793 に答える
3

確かに、スワップを次のように実装できます

template <class T>
void swap(T& x, T& y)
{
  T temp = std::move(x);
  x = std::move(y);
  y = std::move(temp);
}

しかし、たとえばA、より迅速に交換できる独自のクラスがある場合があります。

void swap(A& x, A& y)
{
  using std::swap;
  swap(x.ptr, y.ptr);
}

これは、コンストラクタとデストラクタを実行する代わりに、ポインタを交換するだけです(XCHGまたは同様のものとして実装される可能性があります)。

もちろん、コンパイラは最初の例のコンストラクタ/デストラクタ呼び出しを最適化する場合がありますが、副作用(つまり、new / deleteの呼び出し)がある場合は、それらを最適化するほど賢くない場合があります。

于 2011-06-21T04:41:31.787 に答える
1

交換はできるが移動はできないタイプがあるかもしれません。動かせない活字は知らないので、例はありません。

于 2011-06-20T19:41:20.420 に答える
0

慣例により、習慣swapは投げない保証を提供します。についてはわかりませんstd::swap。それに関する委員会の仕事の私の印象は、それがすべて政治的だったということです、それで彼らがどこかで、または同様の政治的なワードゲーム操作duckとして定義されたとしても私は驚かないでしょう。したがって、C ++ 0xから標準になるまでの詳細なブロークォートを提供しない限り、ここでの回答bugに依存することはありません(確実にノーになるように)。bug

于 2011-06-20T21:10:21.873 に答える
0

メモリに割り当てられたリソースを保持する次のクラスについて考えてみます(簡単にするために、単一の整数で表されます)。

class X {
    int* i_;
public:
    X(int i) : i_(new int(i)) { }
    X(X&& rhs) noexcept : i_(rhs.i_) { rhs.i_ = nullptr; }
 // X& operator=(X&& rhs) noexcept { delete i_; i_ = rhs.i_;
 //                                  rhs.i_ = nullptr; return *this; }
    X& operator=(X rhs) noexcept { swap(rhs); return *this; }
    ~X() { delete i_; }
    void swap(X& rhs) noexcept { std::swap(i_, rhs.i_); }
};

void swap(X& lhs, X& rhs) { lhs.swap(rhs); }

次にstd::swapnullポインターを3回削除します(ムーブ代入演算子と代入演算子統合の両方の場合)。コンパイラは、そのようなものを最適化するのに問題があるかもしれません。https://godbolt.org/g/E84ud4deleteを参照してください。

カスタムswapは何も呼び出さないdeleteため、より効率的である可能性があります。std::unique_ptrこれがカスタムスペシャライゼーションを提供する理由だと思いますstd::swap

アップデート

IntelおよびClangコンパイラはnullポインタの削除を最適化できるようですが、GCCはそうではありません。GCCがC++でnullポインタの削除を最適化しない理由を参照してください。詳細については。

アップデート

GCCでは、次のようdeleteに書き直すことで、演算子の呼び出しを防ぐことができるようです。X

 // X& operator=(X&& rhs) noexcept { if (i_) delete i_; i_ = rhs.i_;
 //                                  rhs.i_ = nullptr; return *this; }
    ~X() { if (i_) delete i_; }
于 2017-08-15T08:33:44.653 に答える