std::move

自C++11开始标准库新增了std::move位于#include<utility>中.

废话不多讲了我们直接来看例子,看例子的过程中需要我们特别注意的是move semantic的触发:

#include <iostream>
#include <type_traits>

class Node {
private:
    int data;

public:
    Node(const int&  data_); //构造函数(constructor).
    Node() = default; //默认构造函数(default-constructor).
    ~Node() { std::cout << "destroy it"<<std::endl; }

    Node(const Node&  other);//拷贝构造函数(copy-contructor)
    Node(Node& &  other);//移动构造函数(move-constructor).

    Node&  operator=(const Node&  other);//拷贝赋值运算符(copy-assignment-operator)
    Node&  operator=(Node& &  other);//移动赋值运算符(move-assignment-operator).

    void setData(const int&  number);

    void printData()const;
};

Node::Node(const int&  data_)
    :data(data_)
{
    //
    std::cout <<  "constructor"  <<  std::endl;
}

Node::Node(const Node&  other)
    : data(other.data)
{
    std::cout << "copy constructor" << std::endl;
}

Node::Node(Node& &  other)
    : data(std::move(other.data))  //注意这里.
{
    std::cout << "move constructor" << std::endl;
}

Node&  Node::operator=(const Node&  other)
{
    this->data = other.data;
    std::cout << "operator=& " <<  std::endl;

    return *this;
}

Node&  Node::operator=(Node& &  other)
{
    this->data = std::move(other.data);     //注意这里.
    std::cout << "operator=& & " <<std::endl;

    return *this;
}

void Node::setData(const int&  number)
{
    this->data = number;
}

void Node::printData()const
{
    std::cout << (this->data)<<std::endl;
}

int main()
{
    //case 1:
    /*Node& &  n1 (Node(20)); //这里只是执行了一个构造函数把20拷贝到Node里面. 
                          //而他的析构函数则是在程序结束的时候才执行一次.

    //case 2:
    Node n(10);           //这里执行了一次普通的构造函数把10拷贝了进去.
    Node&  n2 = n;        //这里是对n1的左值引用(lvalue-reference),
                         //这意味着虽然n2引用了n1但是在程序结束的时候也只是执行n的析构函数.

    //case 3:
    Node(30);            //这里构造函数执行完毕后在执行下面的语句之前又执行了析构函数.
    std::cout <<"---------------" <<std::endl;


    //case 4:
    Node& &  n3(40);      //这里其实是和Node n3(20);一样的效果. */

    //case 5:
    Node& &  n4(50);     //ok.  虽然写的是Node& &  n4(50); 但是n4其实是一个右值引用(lvalue-ref)的左值(lvalue).
    Node&  n5 = n4;     //ok,在程序结束的时候执行n4的析构函数. 
    n5.setData(500);   
    n4.printData();   //输出500.

    //case 6:
    /*Node n6(60);       //普通的构造函数. 在程序结束的时候执行析构函数.
    Node n7 = std::move(n6); //执行移动构造函数.在程序结束的时候执行析构函数.

    //case 7:
    Node n7;          //执行n7的默认构造函数。 在程序结束的时候调用析构函数.
    n7 = Node(70);    //执行n7的移动赋值运算符. 在该行执行完毕后调用 Node(70)的析构函数. */



    return 0;
}

标准库中std::move的实现(>=C++14):

template<typename T>
constexpr typename std::remove_reference<T>::type& &  my_move(T& &  parameter)noexcept
{
    using rvalue_type = std::remove_reference<T>::type& & ;
    return static_cast<rvalue_type>(parameter);
}

move semantic(移动语义)。

这个特性一般来说能够避免非必要的拷贝(copy)和临时对象(temporary).

//cpp98

X x;
std::set<X> coll;

coll.insert(x); //插入x的拷贝值.
coll.insert(x+x); //插入x+x产生的临时值的拷贝.
coll.insert(x;    //插入x的拷贝值(尽管x被插入操作后,不会被再使用).


//cpp11有了move semantic.

X x;
coll.insert(x);  //插入x的拷贝值.
coll.insert(x+x); //移动(也可能是copy)x+x产生的临时值(临时值是个rvalue).
coll.insert(std::move(x)); //移动(也可能是copy)x到coll.

为什么上面也可能是copy呢?

因为具体是否支持move semantic具体还是要看X类型的实现的,而不只是取决于是否使用了std::move().

有了std::move(), X可被move,而不再被拷贝. 然而std::move()本身并不做任何moving操作,它只是将他所接受的实参转换成rvalue-reference.也就是被转换我为X& & 的类型。

从c++11开始:

便有了insert的重载版本:

namespace std{

 template<typename T, ...> class set{
  public:
  ...insert(const T&  value);
  ...insert(T& &  value);
};
}

但是仅仅上面那样是不够的,我们可以优化这个针对rvalue-reference的重载版本,但是为了这么做我们必须使得X支持move,因为只有X才具有接近本质的机会和权力.

假设X是一个很复杂的类型:

X x1;

X x2;

x2 = x1;

这样的话可能开销很大,而且很慢,但是现在有了move semantic,我们可以通过 move构造函数 或者 move赋值运算符 偷取(偷取的前提是x1被偷取后不会再被使用)传入的实参.

class X{

public:

X(const X&  lvalue); //拷贝构造函数.
X(X& &  rvalue); //移动构造函数.

X&  operator=(const X&  lvalue); //拷贝赋值运算符.
X&  operator=(X& &  rvalue);      //移动赋值运算符.

}

如果我们只是加入了 拷贝构造函数 和 拷贝赋值运算符,而move构造函数 和 move赋值运算符不存在,那么当我们std::move的时候也只能调用X对象 copy构造函数 或者 copy赋值运算符(这里涉及到rvalue_ref转为lvalue-ref).

—————————————————————–我是分割线——————————————————-

Rvalue-reference 和 Lvalue-reference的重载规则:

case 1:

void function(X& x);

如果只是实现了上面那个,而没有实现void function(X& & x),只有当传入的值是一个lvalue的时候才能调用function();

case 2:

void function(const X& x);

如果只是实现了上面的,而没有实现void function(X& & x); 无论传入的实参是lvalue 还是 rvalue上面的函数都可以被调用.

case 3:

void function(X& & x);

如果是这种实现,那么当实参是lvalue的时候自动调用 function(const X& )/function(X& ), 如果实参是rvalue的时候自动调用 function(X& & x);

——————————————————我是分割线—————————————————————

返回Rvalue-reference:

X foo()
{
X x;

return x;

}

上面的函数保证有以下行为:

1,如果X类型提供了 copy或者是move构造函数,编译器可能会进行(N)RVO(C++17: copy-elision).

2,否则,如果X有move构造函数那么,优先调用move构造函数。

3,否则调用copy构造函数。

4,否则,报出一个编译器的错误.

也请注意,如果返回的是一个 local variable对象,返回该对象的rvalue-reference是不对的

X& & fucntion()
{
X x;

return x; //error!!!!!!!!!!

}

请注意即使是右值应用代表着它仍然是一个引用,返回一个local variable的引用无论何时都是错误的.

发表评论

电子邮件地址不会被公开。 必填项已用*标注