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

17-强制移动

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

18.2.5 强制移动

移动构造函数和移动赋值运算符使用右值。如果要让它们使用左值,该如何办呢?例如,程序可能分析一个包含候选对象的数组,选择其中一个对象供以后使用,并丢弃数组。如果可以使用移动构造函数或移动赋值运算符来保留选定的对象,那该多好啊。然而,假设您试图像下面这样做:

Useless choices[10];
Useless best;
int pick;
... // select one object, set pick to index
best = choices[pick];

由于choices[pick]是左值,因此上述赋值语句将使用复制赋值运算符,而不是移动赋值运算符。但如果能让choices[pick]看起来像右值,便将使用移动赋值运算符。为此,可使用运算符static_cast<>将对象的类型强制转换为Useless &&,但C++11提供了一种更简单的方式——使用头文件utility中声明的函数std::move()。程序清单18.3演示了这种技术,它在Useless类中添加了啰嗦的赋值运算符,并让以前啰嗦的构造函数和析构函数保持沉默。

程序清单18.3 stdmove.cpp

// stdmove.cpp -- using std::move()
#include <iostream>
#include <utility>
// 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;
    Useless & operator=(const Useless & f); // copy assignment
    Useless & operator=(Useless && f);      // move assignment
    void ShowData() const;
};
// implementation
int Useless::ct = 0;
Useless::Useless()
{
    ++ct;
    n = 0;
    pc = nullptr;
}
Useless::Useless(int k) : n(k)
{
    ++ct;
    pc = new char[n];
}
Useless::Useless(int k, char ch) : n(k)
{
    ++ct;
    pc = new char[n];
    for (int i = 0; i < n; i++)
        pc[i] = ch;
}
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::Useless(Useless && f): n(f.n)
{
    ++ct;
    pc = f.pc;      // steal address
    f.pc = nullptr; // give old object nothing in return
    f.n = 0;
}
Useless::~Useless()
{
    delete [] pc;
}
Useless & Useless::operator=(const Useless & f) // copy assignment
{
    std::cout << "copy assignment operator called:\n";
    if (this == &f)
        return *this;
    delete [] pc;
    n = f.n;
    pc = new char[n];
    for (int i = 0; i < n; i++)
        pc[i] = f.pc[i];
    return *this;
}
Useless & Useless::operator=(Useless && f) // move assignment
{
    std::cout << "move assignment operator called:\n";
    if (this == &f)
        return *this;
    delete [] pc;
    n = f.n;
    pc = f.pc;
    f.n = 0;
    f.pc = nullptr;
    return *this;
}
Useless Useless::operator+(const Useless & f)const
{
    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];
    return temp;
}
void Useless::ShowObject() const
{
    std::cout << "Number of elements: " << n;
    std::cout << " Data address: " << (void *) pc << std::endl;
}
void Useless::ShowData() const
{
    if (n == 0)
        std::cout << "(object empty)";
    else
        for (int i = 0; i < n; i++)
            std::cout << pc[i];
    std::cout << std::endl;
}
// application
int main()
{
    using std::cout;
    {
        Useless one(10, 'x');
        Useless two = one +one; // calls move constructor
        cout << "object one: ";
        one.ShowData();
        cout << "object two: ";
        two.ShowData();
        Useless three, four;
        cout << "three = one\n";
        three = one; // automatic copy assignment
        cout << "now object three = ";
        three.ShowData();
        cout << "and object one = ";
        one.ShowData();
        cout << "four = one + two\n";
        four = one + two; // automatic move assignment
        cout << "now object four = ";
        four.ShowData();
        cout << "four = move(one)\n";
        four = std::move(one); // forced move assignment
        cout << "now object four = ";
        four.ShowData();
        cout << "and object one = ";
        one.ShowData();
    }
}

该程序的输出如下:

object one: xxxxxxxxxx
object two: xxxxxxxxxxxxxxxxxxxx
three = one
copy assignment operator called:
now object three = xxxxxxxxxx
and object one = xxxxxxxxxx
four = one + two
move assignment operator called:
now object four = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
four = move(one)
move assignment operator called:
now object four = xxxxxxxxxx
and object one = (object empty)

正如您看到的,将one赋给three调用了复制赋值运算符,但将move(one)赋给four调用的是移动赋值运算符。

需要知道的是,函数std::move()并非一定会导致移动操作。例如,假设Chunk是一个包含私有数据的类,而您编写了如下代码:

Chunk one;
...
Chunk two;
two = std::move(one); // move semantics?

表达式std::move(one) 是右值,因此上述赋值语句将调用Chunk的移动赋值运算符—— 如果定义了这样的运算符。但如果Chunk没有定义移动赋值运算符,编译器将使用复制赋值运算符。如果也没有定义复制赋值运算符,将根本不允许上述赋值。

对大多数程序员来说,右值引用带来的主要好处并非是让他们能够编写使用右值引用的代码,而是能够使用利用右值引用实现移动语义的库代码。例如,STL类现在都有复制构造函数、移动构造函数、复制赋值运算符和移动赋值运算符。