从可变参数模板走到emplace_back

1,537 阅读3分钟

这是我参与8月更文挑战的第7天,活动详情查看:8月更文挑战

1、可变参数模板

先来简单介绍下可变参数模板:

一个可变参数模板就是一个接受可变数目参数的模板函数或模板类。可变数目的参数被称为参数包:1、模板参数包。2、函数参数包,表示零个或多个参数。

函数可变模板参数定义方式:

void name(){}
template <typename T,typename... Args>
void name(T &t,Args& ... args){
    /*里面传参数包为 args...*/
}

细节:

1、非可变模板参数的版本必须存在,否则可能会造成无线递归;

2、上述函数如果递归调用name(args...);则会把args...分为一个与一包;

可变参数模板类定义方式:

template< class... Types >
class AA;

可变参数模板类的参数包展开的方式和可变参数模板函数的展开方式不同,可变参数模板类的参数包展开需要通过模板特化和继承方式去展开,展开方式比可变参数模板函数要复杂

sizeof...运算符

当我们需要知道包中有多少元素时,可以使用sizeof...运算符。

template <typename... Args> void AA(Args ...args){
    cout<<sizeof...(Args)<<endl;//类型参数数目
    cout<<sizeof...(args)<<endl;//函数参数数目
}

sizeof...(Args)表示参数中有多少种类别的数目,sizeof...(args)表示有多少参数

2、emplace_back与emplace

在我学习这个可变参数模板知识时想起了,请两天在浏览stl的容器是,有这么两个函数emplace_back与emplace,定义分别如下

template <class... Args>
iterator emplace (const_iterator position, Args&&... args);
template <class... Args>
  void emplace_back (Args&&... args);

emplace_back在容器尾部插入元素,设vector<int> ve;

我的第一反应是可以如下操作

ve.emplace_back(100,200,300)但是是错误的,这是纳闷了,不是说好的多参数吗?我又查阅了大量资料,发现不是可变模板参数问题,是函数本身的实现方式是不一样的;此函数是会调用构造函数实现的,我又查阅了函数源码。vector.tcc文件如下:

vector<_Tp, _Alloc>::
      emplace_back(_Args&&... __args)
      {
	if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage)
	  {
	    _GLIBCXX_ASAN_ANNOTATE_GROW(1);
	    _Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish,
				     std::forward<_Args>(__args)...);
	    ++this->_M_impl._M_finish;
	    _GLIBCXX_ASAN_ANNOTATE_GREW(1);
	  }
	else
	  _M_realloc_insert(end(), std::forward<_Args>(__args)...);
#if __cplusplus > 201402L
	return back();
#endif
      }

image-20210728210716477.png

以上调用方法会产生以下实例,可见可变参数模板是没问题的

image-20210728210644957.png

上面提到的问题出在了对参数处理的情况上,当我们把鼠标放到construct上会有以下提示:

image-20210728211002559.png

第一个参数分配器,第二个参数指针-指向一个适合构造tp对象内存大小和对齐的指针,第三个参数构造函数参数

重点来了empalce_back()与emplace()调用的是构造函数,其功能还是插入一个元素对象,虽然可变参数模板可以传多个参数,但没有相应的构造函数匹配,所以错误。接下来讲构造函数可以传多个参数的传递过程。

3、std::forward

声明
lvalue (1)template <class T> T&& forward (typename remove_reference<T>::type& arg) noexcept;
rvalue (2)template <class T> T&& forward (typename remove_reference<T>::type&& arg) noexcept;

功能:

如果不是左值引用,则返回对的右值引用。

如果是左值引用,则函数返回而不修改其类型。

具体传递构造函数参数的过程:

比如str.emplace_back(10,'A');construct调用中的模式会扩展为,std::forward<int>(10),std::forward<char>(A),通过调用forward,保证如果用一个右值调用emplac_back,则construct也会得到一个右值。保证了调用过程的正确性。

以上就是本文了,欢迎大家批评指正。