最近刚刚学习了一下从C++11开始支持的move semantics,C++还是很神奇的。本文不涉及perfect forwarding。
下面代码测试了如下想法
std::vector
。std::vector
进行直接赋值。本机系统 gcc (Ubuntu 7.5.0-3ubuntu1~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次Value的move操作非常迷。values = create_values();
这样创建、初始化和赋值分离的格式,将产生 1次 Value的 move constructor 的调用。create_values()
的局部变量valuesLocal
的内容被move到了函数外部,函数内外valuesLocal
和values
的地址不同。v0 = std::move(v1);
和 v0 = create_value(2);
这样的赋值,将调用1次 move assignement operator。虽然是move assignment,但是Value的实现仍是将成员变量复制了1次,因为成员变量是一个primitive对象。Value v2 = create_value(3);
这样的创建加初始化,没有调用任何move操作, 也没有调用copy assignment operator。temp与v2的地址完全相同。我认为这里被编译器进行了返回值优化,虽然我没有开启任何优化。目前看直接返回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 << "\n";
}
template < typename T >
static void show_seq( const T &seq, const std::string &name ) {
std::cout << name << ": \n";
for( const auto& v : seq ) {
std::cout << v << ", ";
}
std::cout << "\n";
}
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 << "\n";
return temp;
}
static std::vector<Value> create_values() {
std::vector<Value> valuesLocal;
valuesLocal.emplace_back( 0 );
valuesLocal.emplace_back( 1 );
std::cout << "&valuesLocal = " << &valuesLocal << "\n";
std::cout << "valuesLocal.data() = " << valuesLocal.data() << "\n";
std::cout << "&valuesLocal[0] = " << &valuesLocal[0] << "\n";
std::cout << "&valuesLocal[1] = " << &valuesLocal[1] << "\n";
return valuesLocal;
}
int main( int argc, char **argv ) {
std::cout << "Hello, TryMoveSemantics! \n";
{
std::cout << "========== Swap 2 plain arrays. ==========\n";
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 << "\n";
}
{
std::cout << "========== Swap 2 plain arrays of objects. ==========\n";
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 << "\n";
}
{
std::cout << "========== Return std::vector by move. ==========\n";
Value::flag = true;
std::cout << "Creation and initialization. \n";
std::vector<Value> values = create_values();
std::cout << "&values = " << &values << "\n";
std::cout << "values.data() = " << values.data() << "\n";
std::cout << "&values[0] = " << &values[0] << "\n";
std::cout << "&values[1] = " << &values[1] << "\n";
SHOW_SEQ(values)
Value::flag = false;
std::cout << "\n";
}
{
std::cout << "========== Separate creation and assignment. ==========\n";
Value::flag = true;
std::cout << "Separated creation and initialization. \n";
std::vector<Value> values {2, 3};
SHOW_SEQ(values)
values = create_values();
std::cout << "&values = " << &values << "\n";
std::cout << "values.data() = " << values.data() << "\n";
std::cout << "&values[0] = " << &values[0] << "\n";
std::cout << "&values[1] = " << &values[1] << "\n";
SHOW_SEQ(values)
std::vector<Value> valuesLong {2, 3, 4};
std::cout << "&valuesLong = " << &valuesLong << "\n";
std::cout << "valuesLong.data() = " << valuesLong.data() << "\n";
SHOW_SEQ(valuesLong)
valuesLong = create_values(); // Assign size 2 to size 3.
std::cout << "&valuesLong = " << &valuesLong << "\n";
std::cout << "valuesLong.data() = " << valuesLong.data() << "\n";
std::cout << "&valuesLong[0] = " << &valuesLong[0] << "\n";
std::cout << "&valuesLong[1] = " << &valuesLong[1] << "\n";
std::cout << "valuesLong.size() = " << valuesLong.size() << "\n";
SHOW_SEQ(values)
Value::flag = false;
std::cout << "\n";
}
{
std::cout << "========== Assignment by move. ==========\n";
Value::flag = true;
Value v0(0);
Value v1(1);
std::cout << "v0: " << v0 << ", "
<< "v1: " << v1 << "\n";
v0 = std::move(v1);
std::cout << "v0: " << v0 << ", "
<< "v1: " << v1 << "\n";
v0 = create_value(2);
std::cout << "v0: " << v0 << "\n";
std::cout << "Initialization assignment. \n";
Value v2 = create_value(3);
std::cout << "&v2 = " << &v2 << "\n";
std::cout << "v2: " << v2 << "\n";
Value::flag = false;
std::cout << "\n";
}
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