C++
値と参照のセマンティクス
サーチ…
ディープコピーと移動サポート
型が値セマンティクスを持ち、動的に割り当てられたオブジェクトを格納する必要がある場合、コピー操作では、型はそれらのオブジェクトの新しいコピーを割り当てる必要があります。コピーアサインメントでもこれを実行する必要があります。
この種のコピーは「ディープコピー」と呼ばれます。これは、そうでなければ参照セマンティクスを持つセマンティクスを効果的に受け取り、セマンティクスに変換します。
struct Inner {int i;};
const int NUM_INNER = 5;
class Value
{
private:
Inner *array_; //Normally has reference semantics.
public:
Value() : array_(new Inner[NUM_INNER]){}
~Value() {delete[] array_;}
Value(const Value &val) : array_(new Inner[NUM_INNER])
{
for(int i = 0; i < NUM_INNER; ++i)
array_[i] = val.array_[i];
}
Value &operator=(const Value &val)
{
for(int i = 0; i < NUM_INNER; ++i)
array_[i] = val.array_[i];
return *this;
}
};
移動セマンティクスは、参照されたデータを真にコピーしないように、 Value
ような型を許可します。ユーザーが移動を引き起こすような方法で値を使用する場合、オブジェクトからの "コピー"は、参照したデータの空のままにすることができます。
struct Inner {int i;};
constexpr auto NUM_INNER = 5;
class Value
{
private:
Inner *array_; //Normally has reference semantics.
public:
Value() : array_(new Inner[NUM_INNER]){}
//OK to delete even if nullptr
~Value() {delete[] array_;}
Value(const Value &val) : array_(new Inner[NUM_INNER])
{
for(int i = 0; i < NUM_INNER; ++i)
array_[i] = val.array_[i];
}
Value &operator=(const Value &val)
{
for(int i = 0; i < NUM_INNER; ++i)
array_[i] = val.array_[i];
return *this;
}
//Movement means no memory allocation.
//Cannot throw exceptions.
Value(Value &&val) noexcept : array_(val.array_)
{
//We've stolen the old value.
val.array_ = nullptr;
}
//Cannot throw exceptions.
Value &operator=(Value &&val) noexcept
{
//Clever trick. Since `val` is going to be destroyed soon anyway,
//we swap his data with ours. His destructor will destroy our data.
std::swap(array_, val.array_);
}
};
実際に、オブジェクトを移動させながらディープコピーを禁止したい場合は、このようなタイプをコピー不可能にすることもできます。
struct Inner {int i;};
constexpr auto NUM_INNER = 5;
class Value
{
private:
Inner *array_; //Normally has reference semantics.
public:
Value() : array_(new Inner[NUM_INNER]){}
//OK to delete even if nullptr
~Value() {delete[] array_;}
Value(const Value &val) = delete;
Value &operator=(const Value &val) = delete;
//Movement means no memory allocation.
//Cannot throw exceptions.
Value(Value &&val) noexcept : array_(val.array_)
{
//We've stolen the old value.
val.array_ = nullptr;
}
//Cannot throw exceptions.
Value &operator=(Value &&val) noexcept
{
//Clever trick. Since `val` is going to be destroyed soon anyway,
//we swap his data with ours. His destructor will destroy our data.
std::swap(array_, val.array_);
}
};
unique_ptr
使用して、ゼロのルールを適用することもできます。
struct Inner {int i;};
constexpr auto NUM_INNER = 5;
class Value
{
private:
unique_ptr<Inner []>array_; //Move-only type.
public:
Value() : array_(new Inner[NUM_INNER]){}
//No need to explicitly delete. Or even declare.
~Value() = default; {delete[] array_;}
//No need to explicitly delete. Or even declare.
Value(const Value &val) = default;
Value &operator=(const Value &val) = default;
//Will perform an element-wise move.
Value(Value &&val) noexcept = default;
//Will perform an element-wise move.
Value &operator=(Value &&val) noexcept = default;
};
定義
オブジェクトの観察可能な状態が、その型の他のすべてのオブジェクトと機能的に異なる場合、型は値の意味を持ちます。つまり、オブジェクトをコピーすると新しいオブジェクトが作成され、新しいオブジェクトの変更は古いオブジェクトから見えなくなります。
最も基本的なC ++型は、値のセマンティクスを持っています:
int i = 5;
int j = i; //Copied
j += 20;
std::cout << i; //Prints 5; i is unaffected by changes to j.
ほとんどの標準ライブラリで定義された型は、値のセマンティクスも持っています。
std::vector<int> v1(5, 12); //array of 5 values, 12 in each.
std::vector<int> v2 = v1; //Copies the vector.
v2[3] = 6; v2[4] = 9;
std::cout << v1[3] << " " << v1[4]; //Writes "12 12", since v1 is unchanged.
あるタイプのインスタンスがそのオブザーバブル状態を別のオブジェクト(外部のオブジェクト)と共有できる場合、そのタイプの参照セマンティクスは、あるオブジェクトを操作すると別のオブジェクト内で状態が変化するようになります。
C ++ポインタは、それらが指しているオブジェクトに関して値のセマンティクスを持っていますが、それらが指しているオブジェクトの状態に関して参照セマンティクスを持っています。
int *pi = new int(4);
int *pi2 = pi;
pi = new int(16);
assert(pi2 != pi); //Will always pass.
int *pj = pi;
*pj += 5;
std::cout << *pi; //Writes 9, since `pi` and `pj` reference the same object.
C ++リファレンスには参照セマンティクスもあります。