C++11 C++14 C++17 move semantics
最近C++11からサポートされているmove semanticsを勉強したばかりで、C++は不思議です.本明細書ではperfect forwardingには触れない.次のコードは、次のようなアイデアをテストします. swap 2つのplain array. swap 2 array of objects. は、関数から は、 は、関数からオブジェクトを返します. オブジェクトに値を割り当てます.
本システムgcc(Ubuntu 7.5.0-3 ubuntu 1~18.04)7.5.0. コンパイルdebugバージョンは、コンパイル最適化が明示的に開かれていません.
テストによって以下の現象が得られた.単一ステップデバッグにより、swapの2つのplain arrayでは、各要素に対して実際にサイクルが実行されることが分かった.swapの2つのobject arrayでもループが実行され、各要素に対してswapが行われ、move操作が採用されます.一方、2つのarray自体のアドレスは変化せず、オブジェクトのアドレスも変化せず、オブジェクトのメンバー変数(int)の値が入れ替わる. compile time長が等しくないarrayをswapできず、コンパイルできません. は、 は、 は、 は、
現在、
コードは次のとおりです.
プログラムの出力は次のとおりです.
std::vector
を返す.std::vector
を直接付与する.本システムgcc(Ubuntu 7.5.0-3 ubuntu 1~18.04)7.5.0. コンパイルdebugバージョンは、コンパイル最適化が明示的に開かれていません.
テストによって以下の現象が得られた.
std::vector values = create_values();
のように一体的なフォーマットを作成して初期化し、関数create_values()
を介して返されるstd::vector
は、1回のValueのmove constructorを呼び出す.create_values()
の局所変数は、その外部に完全にmoveされ、valuesLocal
およびvalues
のアドレスは完全に一致する.ここではなぜ1回しかバリューのムーブメント操作ができないのか謎です.values = create_values();
のように分離されたフォーマットを作成、初期化、および付与し、1回のValueのmove constructorの呼び出しを生成する.create_values()
の局所変数valuesLocal
の内容は、関数の外部にmoveされ、関数内外のvaluesLocal
とvalues
のアドレスが異なる.v0 = std::move(v1);
およびv0 = create_value(2);
のような単一のオブジェクトに対して、move assignement operatorを1回呼び出す.move assignmentですが、Valueの実装ではメンバー変数がprimitiveオブジェクトであるため、メンバー変数を1回コピーしました.Value v2 = create_value(3);
のような作成と初期化を行い、move操作は呼び出されず、copy assignment operatorも呼び出されなかった.tempはv 2のアドレスと全く同じです.ここではコンパイラによって戻り値の最適化が行われたと思いますが、最適化は行われていません.現在、
std::vector
に直接戻るoverheadは小さく、内部要素はコピーされません.コードは次のとおりです.
#include
#include
#include
#define SHOW_ARRAY(array, n) \
show_array(array, n, #array);
#define SHOW_SEQ(seq) \
show_seq(seq, #seq);
template < typename T >
static void show_array( const T *array, int n, const std::string &name ) {
std::cout << name << " " << array << ": ";
for ( int i = 0; i < n; ++i ) {
std::cout << &array[i] << " " << array[i] << ", ";
}
std::cout << "
";
}
template < typename T >
static void show_seq( const T &seq, const std::string &name ) {
std::cout << name << ":
";
for( const auto& v : seq ) {
std::cout << v << ", ";
}
std::cout << "
";
}
class Value {
public:
Value() = default;
Value(int v)
: val{v}
{}
Value( const Value &other ) {
val = other.val;
}
Value ( Value &&other ) noexcept {
if ( flag ) {
std::cout << "Move constructor. " << std::endl; // Force flush output.
}
val = other.val;
}
~Value() = default;
Value& operator= ( const Value &other ) {
if ( flag ) {
std::cout << "Copy assignment. " << std::endl; // Force flush output.
}
this->val = other.val;
return *this;
}
Value& operator= ( Value &&other ) noexcept {
if ( flag ) {
std::cout << "Move assignment. " << std::endl; // Force flush output.
}
this->val = other.val;
return *this;
}
friend std::ostream& operator<< ( std::ostream &out, const Value &value ) {
out << "val = " << value.val;
return out;
}
public:
int val;
public:
static bool flag;
};
bool Value::flag = false;
static Value create_value(int v) {
Value temp(v);
std::cout << "&temp = " << &temp << "
";
return temp;
}
static std::vector<Value> create_values() {
std::vector<Value> valuesLocal;
valuesLocal.emplace_back( 0 );
valuesLocal.emplace_back( 1 );
std::cout << "&valuesLocal = " << &valuesLocal << "
";
std::cout << "valuesLocal.data() = " << valuesLocal.data() << "
";
std::cout << "&valuesLocal[0] = " << &valuesLocal[0] << "
";
std::cout << "&valuesLocal[1] = " << &valuesLocal[1] << "
";
return valuesLocal;
}
int main( int argc, char **argv ) {
std::cout << "Hello, TryMoveSemantics!
";
{
std::cout << "========== Swap 2 plain arrays. ==========
";
int array0[2] { 0, 1 };
int array1[2] { 2, 3 };
SHOW_ARRAY(array0, 2)
SHOW_ARRAY(array1, 2)
std::swap( array0, array1 );
SHOW_ARRAY(array0, 2)
SHOW_ARRAY(array1, 2)
std::cout << "
";
}
{
std::cout << "========== Swap 2 plain arrays of objects. ==========
";
Value array0[2] { 0, 1 };
Value array1[2] { 2, 3 };
SHOW_ARRAY(array0, 2)
SHOW_ARRAY(array1, 2)
std::swap( array0, array1 );
SHOW_ARRAY(array0, 2)
SHOW_ARRAY(array1, 2)
std::cout << "
";
}
{
std::cout << "========== Return std::vector by move. ==========
";
Value::flag = true;
std::cout << "Creation and initialization.
";
std::vector<Value> values = create_values();
std::cout << "&values = " << &values << "
";
std::cout << "values.data() = " << values.data() << "
";
std::cout << "&values[0] = " << &values[0] << "
";
std::cout << "&values[1] = " << &values[1] << "
";
SHOW_SEQ(values)
Value::flag = false;
std::cout << "
";
}
{
std::cout << "========== Separate creation and assignment. ==========
";
Value::flag = true;
std::cout << "Separated creation and initialization.
";
std::vector<Value> values {2, 3};
SHOW_SEQ(values)
values = create_values();
std::cout << "&values = " << &values << "
";
std::cout << "values.data() = " << values.data() << "
";
std::cout << "&values[0] = " << &values[0] << "
";
std::cout << "&values[1] = " << &values[1] << "
";
SHOW_SEQ(values)
std::vector<Value> valuesLong {2, 3, 4};
std::cout << "&valuesLong = " << &valuesLong << "
";
std::cout << "valuesLong.data() = " << valuesLong.data() << "
";
SHOW_SEQ(valuesLong)
valuesLong = create_values(); // Assign size 2 to size 3.
std::cout << "&valuesLong = " << &valuesLong << "
";
std::cout << "valuesLong.data() = " << valuesLong.data() << "
";
std::cout << "&valuesLong[0] = " << &valuesLong[0] << "
";
std::cout << "&valuesLong[1] = " << &valuesLong[1] << "
";
std::cout << "valuesLong.size() = " << valuesLong.size() << "
";
SHOW_SEQ(values)
Value::flag = false;
std::cout << "
";
}
{
std::cout << "========== Assignment by move. ==========
";
Value::flag = true;
Value v0(0);
Value v1(1);
std::cout << "v0: " << v0 << ", "
<< "v1: " << v1 << "
";
v0 = std::move(v1);
std::cout << "v0: " << v0 << ", "
<< "v1: " << v1 << "
";
v0 = create_value(2);
std::cout << "v0: " << v0 << "
";
std::cout << "Initialization assignment.
";
Value v2 = create_value(3);
std::cout << "&v2 = " << &v2 << "
";
std::cout << "v2: " << v2 << "
";
Value::flag = false;
std::cout << "
";
}
return 0;
}
プログラムの出力は次のとおりです.
Hello, TryMoveSemantics!
========== Swap 2 plain arrays. ==========
array0 0x7ffe93aba190: 0x7ffe93aba190 0, 0x7ffe93aba194 1,
array1 0x7ffe93aba198: 0x7ffe93aba198 2, 0x7ffe93aba19c 3,
array0 0x7ffe93aba190: 0x7ffe93aba190 2, 0x7ffe93aba194 3,
array1 0x7ffe93aba198: 0x7ffe93aba198 0, 0x7ffe93aba19c 1,
========== Swap 2 plain arrays of objects. ==========
array0 0x7ffe93aba190: 0x7ffe93aba190 val = 0, 0x7ffe93aba194 val = 1,
array1 0x7ffe93aba198: 0x7ffe93aba198 val = 2, 0x7ffe93aba19c val = 3,
array0 0x7ffe93aba190: 0x7ffe93aba190 val = 2, 0x7ffe93aba194 val = 3,
array1 0x7ffe93aba198: 0x7ffe93aba198 val = 0, 0x7ffe93aba19c val = 1,
========== Return std::vector by move. ==========
Creation and initialization.
Move constructor.
&valuesLocal = 0x7ffe93aba170
valuesLocal.data() = 0x55de50ea92a0
&valuesLocal[0] = 0x55de50ea92a0
&valuesLocal[1] = 0x55de50ea92a4
&values = 0x7ffe93aba170
values.data() = 0x55de50ea92a0
&values[0] = 0x55de50ea92a0
&values[1] = 0x55de50ea92a4
values:
val = 0, val = 1,
========== Separate creation and assignment. ==========
Separated creation and initialization.
values:
val = 2, val = 3,
Move constructor.
&valuesLocal = 0x7ffe93aba130
valuesLocal.data() = 0x55de50ea92c0
&valuesLocal[0] = 0x55de50ea92c0
&valuesLocal[1] = 0x55de50ea92c4
&values = 0x7ffe93aba110
values.data() = 0x55de50ea92c0
&values[0] = 0x55de50ea92c0
&values[1] = 0x55de50ea92c4
values:
val = 0, val = 1,
&valuesLong = 0x7ffe93aba150
valuesLong.data() = 0x55de50ea92a0
valuesLong:
val = 2, val = 3, val = 4,
Move constructor.
&valuesLocal = 0x7ffe93aba170
valuesLocal.data() = 0x55de50ea92e0
&valuesLocal[0] = 0x55de50ea92e0
&valuesLocal[1] = 0x55de50ea92e4
&valuesLong = 0x7ffe93aba150
valuesLong.data() = 0x55de50ea92e0
&valuesLong[0] = 0x55de50ea92e0
&valuesLong[1] = 0x55de50ea92e4
valuesLong.size() = 2
values:
val = 0, val = 1,
========== Assignment by move. ==========
v0: val = 0, v1: val = 1
Move assignment.
v0: val = 1, v1: val = 1
&temp = 0x7ffe93aba170
Move assignment.
v0: val = 2
Initialization assignment.
&temp = 0x7ffe93aba170
&v2 = 0x7ffe93aba170
v2: val = 3