これは、実際に測定する必要があるケースです。
そして、OPのコピー代入演算子を見て、非効率性を見ています:
A& operator=(A const& other)
{A temp = other; std::swap(*this, temp); return *this;}
*this
とother
が同じ場合はどうなりs
ますか?
if の場合、よりスマートなコピー割り当てにより、ヒープへの移動を回避できるように思えs == other.s
ます。それがしなければならないのはコピーだけです:
A& operator=(A const& other)
{
if (this != &other)
{
if (s != other.s)
{
delete [] p;
p = nullptr;
s = 0;
p = new int[other.s];
s = other.s;
}
std::copy(other.p, other.p + s, this->p);
}
return *this;
}
強力な例外安全性を必要とせず、コピー割り当て ( 、 など) の基本的な例外安全性のみが必要な場合std::string
は、std::vector
上記の方法でパフォーマンスが向上する可能性があります。いくら?測定。
このクラスを次の 3 つの方法でコーディングしました。
デザイン 1:
上記のコピー代入演算子と OP のムーブ代入演算子 #1 を使用します。
デザイン 2:
上記のコピー代入演算子と OP のムーブ代入演算子 #2 を使用します。
デザイン 3:
コピー代入と移動代入の両方に対する DeadMG のコピー代入演算子。
テストに使用したコードは次のとおりです。
#include <cstddef>
#include <algorithm>
#include <chrono>
#include <iostream>
struct A
{
std::size_t s;
int* p;
A(std::size_t s) : s(s), p(new int[s]){}
~A(){delete [] p;}
A(A const& other) : s(other.s), p(new int[other.s])
{std::copy(other.p, other.p + s, this->p);}
A(A&& other) : s(other.s), p(other.p)
{other.s = 0; other.p = nullptr;}
void swap(A& other)
{std::swap(s, other.s); std::swap(p, other.p);}
#if DESIGN != 3
A& operator=(A const& other)
{
if (this != &other)
{
if (s != other.s)
{
delete [] p;
p = nullptr;
s = 0;
p = new int[other.s];
s = other.s;
}
std::copy(other.p, other.p + s, this->p);
}
return *this;
}
#endif
#if DESIGN == 1
// Move assignment operator #1
A& operator=(A&& other)
{
swap(other);
return *this;
}
#elif DESIGN == 2
// Move assignment operator #2
A& operator=(A&& other)
{
delete [] p;
s = other.s;
p = other.p;
other.s = 0;
other.p = nullptr;
return *this;
}
#elif DESIGN == 3
A& operator=(A other)
{
swap(other);
return *this;
}
#endif
};
int main()
{
typedef std::chrono::high_resolution_clock Clock;
typedef std::chrono::duration<float, std::nano> NS;
A a1(10);
A a2(10);
auto t0 = Clock::now();
a2 = a1;
auto t1 = Clock::now();
std::cout << "copy takes " << NS(t1-t0).count() << "ns\n";
t0 = Clock::now();
a2 = std::move(a1);
t1 = Clock::now();
std::cout << "move takes " << NS(t1-t0).count() << "ns\n";
}
これが私が得た出力です:
$ clang++ -std=c++11 -stdlib=libc++ -O3 -DDESIGN=1 test.cpp
$ a.out
copy takes 55ns
move takes 44ns
$ a.out
copy takes 56ns
move takes 24ns
$ a.out
copy takes 53ns
move takes 25ns
$ clang++ -std=c++11 -stdlib=libc++ -O3 -DDESIGN=2 test.cpp
$ a.out
copy takes 74ns
move takes 538ns
$ a.out
copy takes 59ns
move takes 491ns
$ a.out
copy takes 61ns
move takes 510ns
$ clang++ -std=c++11 -stdlib=libc++ -O3 -DDESIGN=3 test.cpp
$ a.out
copy takes 666ns
move takes 304ns
$ a.out
copy takes 603ns
move takes 446ns
$ a.out
copy takes 619ns
move takes 317ns
DESIGN 1
私にはかなり良さそうです。
警告: ミューテックス ロックの所有権やファイルのオープン状態の所有権など、"迅速に" 割り当てを解除する必要があるリソースがクラスにある場合は、正確性の観点から design-2 移動代入演算子の方が適している可能性があります。ただし、リソースが単なるメモリの場合は、(OP のユースケースのように) できるだけ長く割り当て解除を遅らせることが有利な場合がよくあります。
警告 2: 重要であることがわかっている他のユース ケースがある場合は、それらを測定します。あなたは、私がここで得たものとは異なる結論に達するかもしれません。
注:「DRY」よりもパフォーマンスを重視します。ここにあるすべてのコードは、1 つのクラス ( struct A
) 内にカプセル化されます。struct A
できるだけ良くしてください。そして、あなたが十分に質の高い仕事をすれば、あなたのクライアントstruct A
(あなた自身かもしれません)は「RIA」(Reinvent It Again)の誘惑に駆られることはありません。私は、クラス全体の実装を何度も繰り返すよりも、1 つのクラス内で小さなコードを繰り返すことを好みます。