C++14的右值引用能解决什么问题呢?

时间:2022-06-20 19:43:20

现在有这么几个结构体:

typedef struct _info_head{
	u_int src_ip;
	u_int dest_ip;
	u_int src_port; 
	u_int dest_port;
}info_head;

typedef struct _pkt_info{
	u_long sec;
	u_long u_sec;
	u_int pkt_size;
	u_int pld_size;
} pkt_info;

typedef struct _pkt{
	info_head head;
	pkt_info info;
} pkt;

  虽然这几个结构体都是由基本类型构成的,而且也不涉及动态内存管理,但是体积确实是有一点的。其中info_head是16字节,pkt_info是24个字节,那么一个pkt就是40个字节啦。这还只是在32位的机器上的情况。

 

  之后我们又遇到了一个这样的类,这个类使用list容器,存储了多个pkt类型的变量。

class pkt_list{
public:
	static list< pkt > head_to_pkt;

public:
	static int append(const info_head &, const pkt_info &);
};

  下面就来实现一下append()函数吧

int pkt_list::append(const info_head &head, const pkt_info &info){
	pkt tmp;
	tmp.head = head;
	tmp.info = info;
	
	head_to_pkt.push_back(tmp);

	return 1;
}

  由于pkt只包含基本类型,所以直接用就好啦,不用new,也不用担心内存管理的问题。

  这里我们看到了第一个问题,要想把一组info_head和pkt_info类型的变量合并成一个pkt类型的变量,就必须重新组建一个pkt类型的变量。就是说,内存中已经有了16B的info_head和24B的pkt_info之后,但是还要再使用40B去存储和前边一对变量值完全相同的一个pkt类型的变量。虽然有浪费内存的嫌疑,但这个是避免不了的,因为我们没法保证之前的那两个16B和24B的东西就挨在一起。所以其实也不能算是浪费。

  但是接下来又有一个问题,就是把这个pkt类型的变量,搬进list<pkt>里时,内存又做了一次拷贝。因为tmp在append函数结束后就消失了,那么理所当然的,想要在list<pkt> head_to_pkt里留住这个变量的信息,就只好再拷贝一份了。

  那么这样算下来,我们已经用了120B的内存来保存同一段数据了。这样确实是一个很浪费的举动。但是在C++14出现之前,我们无能为力。

  C++14中提出了右值引用的概念,也叫move语义。具体怎么个意义就不说了,直接说效果,那就是可以省下40B的开销。但这是咋做到的呢?很简单,就是直接把第二次的那40B直接移入容器中就可以了,而不是再拷贝复制一次。这么说来,list<T>::push_back(T&);就应该又另一个更高效的版本了,那就是list<T>::push_back(T&&);。总的来说,STL有得重写了。

  接下来,为了使用第二个版本的push_back函数的重载,我们必须在参数列表里写上作为右值的无名变量,但是如果已经使用了一个寄存器变量tmp,那么这40B的开销是无论如何也省不下来的,因此tmp已经有了名字,就不再是右值了。而要想得到pkt类型的无名变量,就要重新为pkt写一个构造函数了。全部改完之后,大概是这个样子的。

class pkt{
public:
	pkt(){}
	pkt(const info_head &h, const pkt_info i):
		head(h), info(i){}

	info_head head;
	pkt_info info;
};

...

int pkt_list::append(const info_head &head, const pkt_info &info){
	head_to_pkt.push_back( pkt( head, info ) );
	//With move semantics version being used, it works.  

	return 1;
}

  OK。就总结这么多。