zoukankan      html  css  js  c++  java
  • C++ vector的emplace_back函数

    C++ STL的vector相信大家一定都知道,它是一个一般用来当做可变长度列表的类。在C++11之前,一般给vector插入新元素用得都是push_back函数,比如下面这样:

    std::vector<std::string> list;
    list.push_back(std::string("6666"));
    

    这种写法事实上有很多的冗余计算,我们来分析下,调用这句push_back一共做了哪些操作:
    1.执行了std::string的构造函数,传入"6666"构造出一个std::string,这是一个临时变量,我们称它为temp;
    2.执行了std::string的拷贝构造函数,将temp的内容拷贝到list的空间中;
    3.执行了std::string的析构函数,析构临时变量temp。
    从中可以看到,我们需要执行std::string的两个构造函数和一个析构函数,还是比较耗费时间的。仔细分析下可以发现,构造函数还是必须要有一个的(因为必须要为list创建一个std::string对象),但我们完全可以省略掉创建和销毁临时对象temp的操作,只要我们能保证将"6666"直接传入为list构造的std::string对象中。
    为了实现这个目标,C++11引入了emplace_back函数,它通过完美转发实现了在vector中插入时直接在容器内构造对象,省略了创建临时对象的操作。我们看下它的代码就不难理解:

    template<class _Ty,
    	class _Alloc = allocator<_Ty>>
    	class vector
    		: public _Vector_alloc<_Vec_base_types<_Ty, _Alloc>>
    {
    ...
    public:
      template<class... _Valty>
      decltype(auto) emplace_back(_Valty&&... _Val)
      {	// insert by perfectly forwarding into element at end, provide strong guarantee
            if (_Has_unused_capacity())
    	{
    	   return (_Emplace_back_with_unused_capacity(_STD forward<_Valty>(_Val)...));
    	}
    
    	_Ty& _Result = *_Emplace_reallocate(this->_Mylast(), _STD forward<_Valty>(_Val)...);
    
    	return (_Result);
      }
    ...
    };
    

    对于上面案例中的list(vectorstd::string)来说,_Ty是std::string,调用list.emplace_back("6666"),则_Valty就是const char*,通过完美转发机制(forward<_Valty>)最终将传入的参数_Val(本例中就是"6666")传入std::string的构造函数中,实现了直接从list中一步到位构造对象,省略了创建临时对象的过程,从而减少了创建的时间。

    下面是我做的实验,首先定义一个类A,它可以接受一个const char*来构造:

    class A
    {
    public:
    	A(const char* c)
    	{
    		int count = strlen(c);
    		content = new char[count];
    		memcpy(content, c, count);
    	}
    
    	~A()
    	{
    		if (content)
    		{
    			delete[] content;
    		}
    	}
    
    	A(const A& a)
    	{
    		int count = strlen(a.content);
    		content = new char[count];
    		memcpy(content, a.content, count);
    	}
    
    	A(A && a)
    	{
    		content = a.content;
    		a.content = nullptr;
    	}
    private:
    	char * content = nullptr;;
    };
    

    通过以下代码来测试运行时间:

    int main()
    {
    	std::vector<A> str_list;
    	std::vector<A> str_list2;
    	std::vector<A> str_list3;
    	std::vector<A> str_list4;
    
    	int count = 100000;
            //提前留好空间,防止创建内存干扰
    	str_list.reserve(count * 2);
    	str_list2.reserve(count * 2);
    	str_list3.reserve(count * 2);
    	str_list4.reserve(count * 2);
    
    	clock_t start = clock();
    	for (int i = 0; i < count; i++)
    	{
    		A a = "hahahaah";
    		str_list.push_back(a);
    	}
    	clock_t end = clock();
    	std::cout << end - start << std::endl;
    
    	start = clock();
    	for (int i = 0; i < count; i++)
    	{
    		str_list2.push_back("hahahaah");
    	}
    	end = clock();
    	std::cout << end - start << std::endl;
    
    	start = clock();
    	for (int i = 0; i < count; i++)
    	{
    		str_list3.emplace_back("hahahaah");
    	}
    	end = clock();
    	std::cout << end - start << std::endl;
    
    	start = clock();
    	for (int i = 0; i < count; i++)
    	{
    		A a = "hahahaah";
    		str_list4.emplace_back(a);
    	}
    	end = clock();
    	std::cout << end - start << std::endl;
    
    	system("pause");
    	return 0;
    }
    

    以上四种情况输出的运行时间如下(VS2017、Debug、Win32模式下编译):

    125
    84
    78
    106
    

    可以看到,最耗时的就是第一种:

    for (int i = 0; i < count; i++)
    {
    	A a = "hahahaah";
    	str_list.push_back(a);
    }
    

    因为它就是本文前面说的“创建临时对象->创建list中的对象->销毁临时对象”过程。

    str_list2的做法相对好一些,因为虽然也会生成临时对象,但因为传入的是右值引用,因此调用的是移动构造函数,相对来说节省一些。
    str_list3就是本文上面提到的做法,直接在vector上构造对象,自然是时间最短的。
    str_list4还是构造了对象,因此虽然调用的是emplace_back,但传入的是左值引用A&,所以仍然比较耗时间。不过这也说明,emplace_back也可以像push_back一样传入要插入的对象(而不是构造对象的参数),这样的话走的还是类似push_back的流程,通过调用复制构造函数创建新对象。这种用法从试验结果上看,起码不会比push_back性能更差。

  • 相关阅读:
    mysql日志
    MYSQL-事务中的锁与隔离级别
    MYSQL事务的日志
    Innodb核心特性-事务
    Innodb存储引擎-表空间介绍
    innodb存储引擎简介
    MYSQL之存储引擎
    MYSQL之索引配置方法分类
    MYSQL之索引算法分类
    MYSQL之select的高级用法
  • 原文地址:https://www.cnblogs.com/wickedpriest/p/15113545.html
Copyright © 2011-2022 走看看