深入解析C++右值引用和移动语义:编写更快、更节省内存的代码_c+(1)

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

cout << "main finish" << endl;

return 0;

}


执行结果:



constructor A
constructor A
ready return
copy constructor A
destructor A, m_ptr:0x184e2a0
destructor A, m_ptr:0x184de70
destructor A, m_ptr:0x184e2c0
main finish


(3)移动构造函数。  
 这样深拷贝构造函数就可以保证拷贝构造时的安全性,但有时这种拷贝构造却是不必要的,比如上面代码中的拷贝构造就是不必要的。上面代码中的 Get 函数会返回临时变量,然后通过这个临时变量拷贝构造了一个新的对象 b,临时变量在拷贝构造完成之后就销毁了,**如果堆内存很大,那么,这个拷贝构造的代价会很大,带来了额外的性能损耗**。有没有办法避免临时对象的拷贝构造呢?看下面的代码:



#include
using namespace std;

class A {
public:
A() :m_ptr(new int(0))
{
cout << “constructor A” << endl;
}
A(const A& a) :m_ptr(new int(*a.m_ptr))
{
cout << “copy constructor A” << endl;
}
// 移动构造函数,可以浅拷贝
A(A&& a):m_ptr(a.m_ptr)
{
// 为防止a析构时delete data,提前置空其m_ptr
a.m_ptr = nullptr;
cout << “move constructor A” << endl;
}
~A()
{
cout << “destructor A, m_ptr:” << m_ptr << endl;
delete m_ptr;
m_ptr = nullptr;
}
private:
int *m_ptr;
};

// 为了避免返回值优化,此函数故意这样写
A Get(bool flag)
{
A a;
A b;
cout << “ready return” << endl;
if (flag)
return a;

return b;

}

int main(int argc, char **argv)
{
{
A a = Get(false);
}

cout << "main finish" << endl;

return 0;

}


上面的代码中没有了拷贝构造,取而代之的是移动构造( Move Construct)。从移动构造函数的实现中可以看到,它的参数是一个右值引用类型的参数 A&&,这里没有深拷贝,只有浅拷贝,这样就避免了对临时对象的深拷贝,提高了性能。这里的 A&& 用来根据参数是左值还是右值来建立分支,如果是临时值,则会选择移动构造函数。移动构造函数只是将临时对象的资源做了浅拷贝,不需要对其进行深拷贝,从而避免了额外的拷贝,提高性能。这也就是所谓的移动语义( move 语义),**右值引用的一个重要目的是用来支持移动语义的**。


执行结果:



constructor A
constructor A
ready return
move constructor A
destructor A, m_ptr:0
destructor A, m_ptr:0x1eb9e70
destructor A, m_ptr:0x1eba2a0
main finish


移动语义可以将资源(堆、系统对象等)通过浅拷贝方式从一个对象转移到另一个对象,这样能够减少不必要的临时对象的创建、拷贝以及销毁,可以大幅度提高 C++ 应用程序的性能,消除临时对象的维护(创建和销毁)对性能的影响。


### 5.2、移动(move)语义


move是将对象的状态或者所有权从一个对象转移到另一个对象,只是转义,没有内存拷贝。要move语义起作用,核心在于需要对应类型的构造函数支持。




#mermaid-svg-FM4CXq96SVfvN2XN {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-FM4CXq96SVfvN2XN .error-icon{fill:#552222;}#mermaid-svg-FM4CXq96SVfvN2XN .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-FM4CXq96SVfvN2XN .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-FM4CXq96SVfvN2XN .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-FM4CXq96SVfvN2XN .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-FM4CXq96SVfvN2XN .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-FM4CXq96SVfvN2XN .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-FM4CXq96SVfvN2XN .marker{fill:#333333;stroke:#333333;}#mermaid-svg-FM4CXq96SVfvN2XN .marker.cross{stroke:#333333;}#mermaid-svg-FM4CXq96SVfvN2XN svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-FM4CXq96SVfvN2XN .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-FM4CXq96SVfvN2XN .cluster-label text{fill:#333;}#mermaid-svg-FM4CXq96SVfvN2XN .cluster-label span{color:#333;}#mermaid-svg-FM4CXq96SVfvN2XN .label text,#mermaid-svg-FM4CXq96SVfvN2XN span{fill:#333;color:#333;}#mermaid-svg-FM4CXq96SVfvN2XN .node rect,#mermaid-svg-FM4CXq96SVfvN2XN .node circle,#mermaid-svg-FM4CXq96SVfvN2XN .node ellipse,#mermaid-svg-FM4CXq96SVfvN2XN .node polygon,#mermaid-svg-FM4CXq96SVfvN2XN .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-FM4CXq96SVfvN2XN .node .label{text-align:center;}#mermaid-svg-FM4CXq96SVfvN2XN .node.clickable{cursor:pointer;}#mermaid-svg-FM4CXq96SVfvN2XN .arrowheadPath{fill:#333333;}#mermaid-svg-FM4CXq96SVfvN2XN .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-FM4CXq96SVfvN2XN .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-FM4CXq96SVfvN2XN .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-FM4CXq96SVfvN2XN .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-FM4CXq96SVfvN2XN .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-FM4CXq96SVfvN2XN .cluster text{fill:#333;}#mermaid-svg-FM4CXq96SVfvN2XN .cluster span{color:#333;}#mermaid-svg-FM4CXq96SVfvN2XN div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-FM4CXq96SVfvN2XN :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}































移动构造
























move




















resources









SourceObject









DestObject













拷贝构造

































copy











resources









SourceObject









DestObject









resources












#include
#include
#include
#include
#include <string.h>

using namespace std;

class MyString
{
public:
MyString()
{
m_data = NULL;
m_len = 0;
}
MyString(const char *p)
{
m_len = strlen§;
copy_data§;
}
MyString(const MyString& str)
{
m_len = str.m_len;
copy_data(str.m_data);
std::cout << "Copy Constructor is called! source: " << str.m_data << std::endl;
}
MyString& operator=(const MyString& str)
{
if (this != &str)
{
m_len = str.m_len;
copy_data(str.m_data);
}
std::cout << "Copy Assignment is called! source: " << str.m_data << std::endl;
return *this;
}
// 用c++11的右值引用来定义这两个函数
MyString(MyString&& str)
{
std::cout << "Move Constructor is called! source: " << str.m_data << std::endl;
m_len = str.m_len;
m_data = str.m_data; //避免了不必要的拷贝
str.m_len = 0;
str.m_data = NULL;
}
MyString& operator=(MyString&& str)
{
std::cout << "Move Assignment is called! source: " << str.m_data << std::endl;
if (this != &str)
{
m_len = str.m_len;
m_data = str.m_data; //避免了不必要的拷贝
str.m_len = 0;
str.m_data = NULL;
}
return *this;
}
virtual ~MyString()
{
if (m_data)
free(m_data);
}

private:
char * m_data;
size_t m_len;
void copy_data(const char *s)
{
m_data = new char[m_len + 1];
memcpy(m_data, s, m_len);
m_data[m_len] = ‘\0’;
}
};

int main()
{
MyString a;
a = MyString(“Hello”);// Move Assignment
MyString b = a;// Copy Constructor
MyString c = std::move(a);// Move Constructor is called! 将左值转为右值

std::vector<MyString> vec; 
vec.push\_back(MyString("World")); // Move Constructor is called!

return 0;

}


执行结果:



Move Assignment is called! source: Hello
Copy Constructor is called! source: Hello
Move Constructor is called! source: Hello
Move Constructor is called! source: World


有了右值引用和转移语义,我们在设计和实现类时,对于需要动态申请大量资源的类,应该设计右值引用的拷贝构造函数和赋值函数,以提高应用程序的效率。


### 5.3、forward 完美转发


forward 完美转发实现了参数**在传递过程中保持其值属性**的功能,即若是左值,则传递之后仍然是左值,若是右值,则传递之后仍然是右值。


现存在一个函数。



Template
void func(T &&val);


根据前面所描述的,这种引用类型既可以对左值引用,亦可以对右值引用。但要注意,引用以后,**这个val值它本质上是一个左值**!  
 看下面例子:



int &&a = 10;
int &&b = a; //错误


a是一个右值引用,但其本身a也有内存名字,所以a本身是一个左值,再用右值引用引用a这是不对的。


因此有了std::forward()完美转发,这种T &&val中的val是左值,但如果用std::forward (val),就会按照参数原来的类型转发。



int &&a = 10;
int &&b = std::forward(a);


示例:



#include
using namespace std;

template
void Print(T &t)
{
cout << “L” << t << endl;
}
template
void Print(T &&t)
{
cout << “R” << t << endl;
}
template
void func(T &&t)
{
Print(t);
Print(std::move(t));
Print(std::forward(t));
}

int main() {
cout << “-- func(1)” << endl;
func(1);
int x = 10;
int y = 20;
cout << “-- func(x)” << endl;
func(x); // x本身是左值
cout << “-- func(std::forward(y))” << endl;
func(std::forward(y));

cout << "-- func(std::forward<int&>(y))" << endl;
func(std::forward<int&>(y));
return 0; 

}


执行结果:



– func(1)
L1
R1
R1
– func(x)
L10
R10
L10
– func(std::forward(y))
L20
R20
R20
– func(std::forward<int&>(y))
L20
R20
L20


**分析:** func(1)由于1是右值,所以未定的引用类型T&&v被一个右值初始化后变成了一个右值引用,但是在func()函数体内部,调用PrintT(v) 时,v又变成了一个左值(因为在std::forward里它已经变成了一个具名的变量,所以它是一个左值),因此,示例测试结果第一个PrintT被调用,打印出“L1"调用PrintT(std::forward(v))时,由于std::forward会按参数原来的类型转发,因此,它还是一个右值(这里已经发生了类型推导,所以这里的T&&不是一个未定的引用类型,会调用void PrintT(T&&t)函  
 数打印 “R1”.调用PrintT(std::move(v))是将v变成一个右值(v本身也是右值),因此,它将输出”R1"func(x)未定的引用类型T&&v被一个左值初始化后变成了一个左值引用,因此,在调用PrintT(std::forward(v))时它会被转发到void PrintT(T&t)。


### 5.4、综合示例



#include “stdio.h”
#include
#include
#include

using namespace std;

class A
{
public:
int *m_ptr = NULL; // 增加初始化
int m_nSize = 0;

A() :m\_ptr(NULL), m\_nSize(0) {}
A(int \*ptr, int nSize)
{
	m_nSize = nSize;
	m_ptr = new int[nSize];
	printf("A(int \*ptr, int nSize) m\_ptr:%p\n", m_ptr);
	if (m_ptr)
	{
		memcpy(m_ptr, ptr, sizeof(sizeof(int) \* nSize));
	}
}
A(const A&other)// 拷贝构造函数实现深拷贝
{
	m_nSize = other.m_nSize;
	if (other.m_ptr)
	{
		printf("A(const A &other) m\_ptr:%p\n", m_ptr);
		if (m_ptr)
			delete[] m_ptr;
		printf("delete[] m\_ptr\n");

		m_ptr = new int[m_nSize];
		memcpy(m_ptr, other.m_ptr, sizeof(sizeof(int) \* m_nSize));
	}
	else
	{
		if (m_ptr)
			delete[] m_ptr;
		m_ptr = NULL;
	}
	cout << "A(const int &i)" << endl;
}
// 右值应用构造函数
A(A &&other)
{
	m_ptr = NULL;
	m_nSize = other.m_nSize;
	if (other.m_ptr)
	{
		m_ptr = move(other.m_ptr);
		other.m_ptr = NULL;
	}
}

~A()
{
	if (m_ptr)
	{
		delete[] m_ptr;
		m_ptr = NULL;
	}
}
void deleteptr() 
{ 
	if (m_ptr) 
	{ 
		delete[] m_ptr; 
		m_ptr = NULL; 
	} 
}

};

int main() {
int arr[] = { 1, 2, 3 };
A a(arr, sizeof(arr) / sizeof(arr[0]));
cout << “m_ptr in a Addr: 0x” << a.m_ptr << endl;
A b(a);
cout << “m_ptr in b Addr: 0x” << b.m_ptr << endl; b.deleteptr();
A c(std::forward(a)); // 完美转换
cout << “m_ptr in c Addr: 0x” << c.m_ptr << endl; c.deleteptr();
vector vect{1, 2, 3, 4, 5};
cout << "before move vect size: " << vect.size() << endl;
vector vect1 = move(vect);
cout << "after move vect size: " << vect.size() << endl;
cout << "new vect1 size: " << vect1.size() << endl;
return 0;
}


执行结果:



A(int *ptr, int nSize) m_ptr:0x219ce70
m_ptr in a Addr: 0x0x219ce70
A(const A &other) m_ptr:(nil)
delete[] m_ptr
A(const int &i)
m_ptr in b Addr: 0x0x219d2a0
m_ptr in c Addr: 0x0x219ce70
before move vect size: 5
after move vect size: 0
new vect1 size: 5


### 5.5、emplace\_back 减少内存拷贝和移动


对于STL容器,C++11后引入了emplace\_back接口。  
 emplace\_back是就地构造,不用构造后再次复制到容器中。因此效率更高。


考虑这样的语句:



vector testVec;
testVec.push_back(string(16,‘q’));


上述语句足够简单易懂,将一个string对象添加到testVec中。底层实现:  
 (1)首先,string(16, ‘a’)会创建一个string类型的临时对象,这涉及到一次string构造过程。  
 (2)其次,vector内会创建一个新的string对象,这是第二次构造。  
 (3)最后在push\_back结束时,最开始的临时对象会被析构。加在一起,这两行代码会涉及到两次string构造和一次析构。


**c++11可以用emplace\_back代替push\_back,emplace\_back可以直接在vector中构建一个对象,而非创建一个临时对象,再放进vector,再销毁。emplace\_back可以省略一次构建和一次析构,从而达到优化的目的。**


测试示例:



#include
#include
#include
#include
#ifdef GCC
#include <sys/time.h>
#else
#include
#endif
// GCC
class TimeInterval
{
public:
TimeInterval(const std::string& d) : detail(d)
{
init();
}
TimeInterval()
{
init();
}
~TimeInterval()
{
#ifdef GCC
gettimeofday(&end, NULL);
std::cout << detail << 1000 * (end.tv_sec - start.tv_sec) + (end.tv_usec - start.tv_usec) / 1000 << " ms" << endl;
#else
end = clock();
std::cout << detail << (double)(end - start) << " ms" << std::endl;
#endif // GCC
}
protected:
void init()
{
#ifdef GCC
gettimeofday(&start, NULL);
#else
start = clock();
#endif // GCC
}
private:
std::string detail;
#ifdef GCC
timeval start, end;
#else
clock_t start, end;
#endif // GCC
};

#define TIME_INTERVAL_SCOPE(d) std::shared_ptr time_interval_scope_begin = std::make_shared(d)

int main() {
std::vectorstd::string v;
int count = 100000;
v.reserve(count); //预分配十万大小,排除掉分配内存的时间
{
TIME_INTERVAL_SCOPE(“push_back string:”);
for (int i = 0; i < count; i++)
{
std::string temp(“test”);
v.push_back(temp);// push_back(const string&),参数是左值引用
}
}
v.clear();
{
TIME_INTERVAL_SCOPE(“push_back move(string):”);
for (int i = 0; i < count; i++)
{
std::string temp(“test”);
v.push_back(std::move(temp));// push_back(string &&), 参数是右值引用
}
}
v.clear();
{
TIME_INTERVAL_SCOPE(“push_back(string):”);

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

_INTERVAL_SCOPE(“push_back string:”);
for (int i = 0; i < count; i++)
{
std::string temp(“test”);
v.push_back(temp);// push_back(const string&),参数是左值引用
}
}
v.clear();
{
TIME_INTERVAL_SCOPE(“push_back move(string):”);
for (int i = 0; i < count; i++)
{
std::string temp(“test”);
v.push_back(std::move(temp));// push_back(string &&), 参数是右值引用
}
}
v.clear();
{
TIME_INTERVAL_SCOPE(“push_back(string):”);

[外链图片转存中…(img-H1lEo7Yq-1715884237198)]
[外链图片转存中…(img-OjNimw5A-1715884237198)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

  • 7
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值