14-一个移动示例
18.2.2 一个移动示例
下面通过一个示例演示移动语义和右值引用的工作原理。程序清单18.2定义并使用了Useless类,这个类动态分配内存,并包含常规复制构造函数和移动构造函数,其中移动构造函数使用了移动语义和右值引用。为演示流程,构造函数和析构函数都比较啰嗦,同时Useless类还使用了一个静态变量来跟踪对象数量。另外,省略了一些重要的方法,如赋值运算符。
程序清单18.2 useless.cpp
// useless.cpp -- an otherwise useless class with move semantics
#include <iostream>
using namespace std;
// interface
class Useless
{
private:
int n; // number of elements
char * pc; // pointer to data
static int ct; // number of objects
void ShowObject() const;
public:
Useless();
explicit Useless(int k);
Useless(int k, char ch);
Useless(const Useless & f); // regular copy constructor
Useless(Useless && f); // move constructor
~Useless();
Useless operator+(const Useless & f)const;
// need operator=() in copy and move versions
void ShowData() const;
};
// implementation
int Useless::ct = 0;
Useless::Useless()
{
++ct;
n = 0;
pc = nullptr;
cout << "default constructor called; number of objects: " << ct << endl;
ShowObject();
}
Useless::Useless(int k) : n(k)
{
++ct;
cout << "int constructor called; number of objects: " << ct << endl;
pc = new char[n];
ShowObject();
}
Useless::Useless(int k, char ch) : n(k)
{
++ct;
cout << "int, char constructor called; number of objects: " << ct
<< endl;
pc = new char[n];
for (int i = 0; i < n; i++)
pc[i] = ch;
ShowObject();
}
Useless::Useless(const Useless & f): n(f.n)
{
++ct;
cout << "copy const called; number of objects: " << ct << endl;
pc = new char[n];
for (int i = 0; i < n; i++)
pc[i] = f.pc[i];
ShowObject();
}
Useless::Useless(Useless && f): n(f.n)
{
++ct;
cout << "move constructor called; number of objects: " << ct << endl;
pc = f.pc; // steal address
f.pc = nullptr; // give old object nothing in return
f.n = 0;
ShowObject();
}
Useless::~Useless()
{
cout << "destructor called; objects left: " << --ct << endl;
cout << "deleted object:\n";
ShowObject();
delete [] pc;
}
Useless Useless::operator+(const Useless & f)const
{
cout << "Entering operator+()\n";
Useless temp = Useless(n + f.n);
for (int i = 0; i < n; i++)
temp.pc[i] = pc[i];
for (int i = n; i < temp.n; i++)
temp.pc[i] = f.pc[i - n];
cout << "temp object:\n";
cout << "Leaving operator+()\n";
return temp;
}
void Useless::ShowObject() const
{
cout << "Number of elements: " << n;
cout << " Data address: " << (void *) pc << endl;
}
void Useless::ShowData() const
{
if (n == 0)
cout << "(object empty)";
else
for (int i = 0; i < n; i++)
cout << pc[i];
cout << endl;
}
// application
int main()
{
{
Useless one(10, 'x');
Useless two = one; // calls copy constructor
Useless three(20, 'o');
Useless four (one + three); // calls operator+(), move constructor
cout << "object one: ";
one.ShowData();
cout << "object two: ";
two.ShowData();
cout << "object three: ";
three.ShowData();
cout << "object four: ";
four.ShowData();
}
}
其中最重要的是复制构造函数和移动构造函数的定义。首先来看复制构造函数(删除了输出语句):
Useless::Useless(const Useless & f): n(f.n)
{
++ct;
pc = new char[n];
for (int i = 0; i < n; i++)
pc[i] = f.pc[i];
}
它执行深复制,是下面的语句将使用的构造函数:
Useless two = one; // calls copy constructor
引用f将指向左值对象one。
接下来看移动构造函数,这里也删除了输出语句:
Useless::Useless(Useless && f): n(f.n)
{
++ct;
pc = f.pc; // steal address
f.pc = nullptr; // give old object nothing in return
f.n = 0;
}
它让pc指向现有的数据,以获取这些数据的所有权。此时,pc和f.pc指向相同的数据,调用析构函数时这将带来麻烦,因为程序不能对同一个地址调用delete [ ]两次。为避免这种问题,该构造函数随后将原来的指针设置为空指针,因为对空指针执行delete [ ]没有问题。这种夺取所有权的方式常被称为窃取(pilfering)。上述代码还将原始对象的元素数设置为零,这并非必不可少的,但让这个示例的输出更一致。注意,由于修改了f对象,这要求不能在参数声明中使用const。
在下面的语句中,将使用这个构造函数:
Useless four (one + three); // calls move constructor
表达式one + three调用Useless::operator+(),而右值引用f将关联到该方法返回的临时对象。
下面是在Microsoft Visual C++ 2010中编译时,该程序的输出:
int, char constructor called; number of objects: 1
Number of elements: 10 Data address: 006F4B68
copy const called; number of objects: 2
Number of elements: 10 Data address: 006F4BB0
int, char constructor called; number of objects: 3
Number of elements: 20 Data address: 006F4BF8
Entering operator+()
int constructor called; number of objects: 4
Number of elements: 30 Data address: 006F4C48
temp object:
Leaving operator+()
move constructor called; number of objects: 5
Number of elements: 30 Data address: 006F4C48
destructor called; objects left: 4
deleted object:
Number of elements: 0 Data address: 00000000
object one: xxxxxxxxxx
object two: xxxxxxxxxx
object three: oooooooooooooooooooo
object four: xxxxxxxxxxoooooooooooooooooooo
destructor called; objects left: 3
deleted object:
Number of elements: 30 Data address: 006F4C48
destructor called; objects left: 2
deleted object:
Number of elements: 20 Data address: 006F4BF8
destructor called; objects left: 1
deleted object:
Number of elements: 10 Data address: 006F4BB0
destructor called; objects left: 0
deleted object:
Number of elements: 10 Data address: 006F4B68
注意到对象two是对象one的副本:它们显示的数据输出相同,但显示的数据地址不同(006F4B68和006F4BB0)。另一方面,在方法Useless::operator+()中创建的对象的数据地址与对象four存储的数据地址相同(都是006F4C48),其中对象four是由移动复制构造函数创建的。另外,注意到创建对象four后,为临时对象调用了析构函数。之所以知道这是临时对象,是因为其元素数和数据地址都是0。
如果使用编译器g++ 4.5.0和标记-std=c++11编译该程序(但将nullptr替换为0),输出将不同,这很有趣:
int, char constructor called; number of objects: 1
Number of elements: 10 Data address: 0xa50338
copy const called; number of objects: 2
Number of elements: 10 Data address: 0xa50348
int, char constructor called; number of objects: 3
Number of elements: 20 Data address: 0xa50358
Entering operator+()
int constructor called; number of objects: 4
Number of elements: 30 Data address: 0xa50370
temp object:
Leaving operator+()
object one: xxxxxxxxxx
object two: xxxxxxxxxx
object three: oooooooooooooooooooo
object four: xxxxxxxxxxoooooooooooooooooooo
destructor called; objects left: 3
deleted object:
Number of elements: 30 Data address: 0xa50370
destructor called; objects left: 2
deleted object:
Number of elements: 20 Data address: 0xa50358
destructor called; objects left: 1
deleted object:
Number of elements: 10 Data address: 0xa50348
destructor called; objects left: 0
deleted object:
Number of elements: 10 Data address: 0xa50338
注意到没有调用移动构造函数,且只创建了4个对象。创建对象four时,该编译器没有调用任何构造函数;相反,它推断出对象four是operator+() 所做工作的受益人,因此将operator+()创建的对象转到four的名下。一般而言,编译器完全可以进行优化,只要结果与未优化时相同。即使您省略该程序中的移动构造函数,并使用g++进行编译,结果也将相同。