C++11:移动语义Move Semantics和完美转发Perfect Forwarding

Posted 木大白易

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++11:移动语义Move Semantics和完美转发Perfect Forwarding相关的知识,希望对你有一定的参考价值。

右值引用到底用来干啥

关于右值引用的说明,我们在上一篇文章中已经通过具体的例子来了解了!那么它到底有什么用?

我这里引用一下一篇2006年非常经典的论文的原文:

Question: Why on Earth would we want to do this?!

It turns out that the combination of rvalue references and lvalue references is just what is needed to easily code move semantics. The rvalue reference can also be used to achieve perfect forwarding, a heretofore unsolved problem in C++. From a casual programmer’s perspective, what we get from rvalue references is more general and better performing libraries.

文章来自:A Brief Introduction to Rvalue References
推荐大家阅读一下!

这里简单翻译一下就是:事实证明,右值引用和左值引用的组合正是轻松编码移动语义所需要的。 右值引用也可用于实现完美转发,这是 C++ 中迄今为止未解决的问题。 从普通程序员的角度来看,我们从右值引用中得到的是更通用、性能更好的库。

移动语义

先说几点结论:

  1. C++ 标准库使用比如vector::push_back 等这类函数时,会对参数的对象进行复制,连数据也会复制.这就会造成对象内存的额外创建, 本来原意是想把参数push_back进去就行了,通过std::move,可以避免不必要的拷贝操作。
  2. std::move是将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存的搬迁或者内存拷贝所以可以提高利用效率,改善性能.。
  3. 对指针类型的标准库对象并不需要这么做.

再来看一个例子:

class A

public:
    string a;

	A() 
		a = "init value";
		cout << "constructor A" << endl;
	
	A(const A& other) 
		cout << "copy constructor A" << endl;
	
	~A() 
		cout << "destructor A" << endl;
	

    A(A&& other) : a(std::move(other.a))
        cout << "move constructor A" << endl;
    

    A& operator=(A&& other)
        cout << "move assignment operator A" << endl;
        a = std::move(other.a);
        return *this;
    
;

int main(void) 
	
	int a = 6;
	int &&b = std::move(a);
	cout << "b = "<<++b << endl;//b=7
	cout << "a = "<<a << endl;//a=7
 
	string s = "hello";
	vector<string> vec;
	//调用常规的拷贝构造函数,新建字符串,拷贝数据
	vec.push_back(s);
	cout << "after copy: "<< s << endl;
	
	//调用移动构造函数
	vec.push_back(std::move(s));
	cout << "after move: " << s << endl;   //空字符串
 
	A aa;
	vector<A> vec_A;
	vec_A.push_back(aa);
    cout << "after copy: "<< aa.a << endl;
 
	//调用移动构造函数  没有move构造的时候,走的copy构造
	vec_A.push_back(std::move(aa));
    cout << "after move: "<< aa.a << endl;   //空字符串
 
 
	return 0;

从上边这个例子中,可以比较清晰的看到,当用一个对象去构造另一个对象,且原对象不再使用的时候,可以使用move构造,去省去不必要的对象copy,从而提升性能!
c++11开始,stringvector已经实现了move语义!这将完全避免极其昂贵的复制操作。

完美转发

这里我们仍然看一下例子:

template <class  T>
std::shared_ptr<T>
factory()
    return std::shared_ptr<T>(new T);


template <class  T, class A1>
std::shared_ptr<T>
factory(const A1& a1)
    return std::shared_ptr<T>(new T(a1));


//所有其他版本

class A

private:
    int memeber;
public:
    A(int a):memeber(a);
    ~A();
;



int main()
    std::shared_ptr<A> p = factory<A>(5); //正确
    return 0;

上边会匹配到第二个单参数模板,但是如果我们传入一个非const参数,则会报错,因为我们的模板函数中限定了参数为const!
这时候,我们可以将模板函数中的const去掉:

template <class  T, class A1>
std::shared_ptr<T>
factory(A1& a1)  //这里去掉const
    return std::shared_ptr<T>(new T(a1));

如果将 const 限定类型传递给factory,则 const 将被推导到模板参数(例如A1)中,然后正确转发给T的构造函数。同样,如果给 factory一个非常量参数,它将作为非常量正确地转发给T的构造函数。事实上,这正是当今转发应用程序的编码方式(例如std::bind)。

但是,这样的话,最开始的传入一个int参数的值就会编译错误:

std::shared_ptr<A> p = factory<A>(5); //错误

因为这里模板会被推断为参数类型为int&,但是5是一个右值,所以没有模板会被匹配到

所以我们想再添加一个原来版本的带有const限定参数的模板,但是这样做代价会非常大!

那么这时候右值引用就闪亮登场了,可以完美解决这个问题:

template <class  T, class A1>
std::shared_ptr<T>
factory(A1&& a1)
    return std::shared_ptr<T>(new T(std::forward<A1>(a1)));

move()一样,forward()是一个简单的标准库函数,用于直接和明确地表达我们的意图,而不是通过潜在的隐蔽使用引用。

在这里,forward()保留了传递给factory()的参数的左值/右值 。如果一个右值被传递给factory(),那么一个右值将在forward()函数的帮助下被传递给T的构造函数。类似地,如果将左值传递给factory(),它会作为左值转发给 T的构造函数。

推荐阅读

C++ Rvalue References Explained

以上是关于C++11:移动语义Move Semantics和完美转发Perfect Forwarding的主要内容,如果未能解决你的问题,请参考以下文章

C++11:移动语义Move Semantics和完美转发Perfect Forwarding

Move semantics(C++11)

C++11的右值引用移动语义(std::move)和完美转发(std::forward)详解

[C++11] --- 移动语义和完美转发

[C++11] --- 移动语义和完美转发

[C++11] --- 移动语义和完美转发