当前位置:嗨网首页>书籍在线阅读

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++进行编译,结果也将相同。