C++雾中风景9:emplace_back与可变长模板

C++11的版本在vector容器添加了emplace_back方法,相对于原先的push_back方法能够在一定程度上提升vector容器的表现性能。所以我们从STL源码角度来切入,看看这两种方法有什么样的区别,新引进的方法又有什么可学习参考之处。

1.emplace_back的用法

emplace_back方法最大的改进就在与可以利用类本身的构造函数直接在内存之中构建对象,而不需要调用类的拷贝构造函数移动构造函数

举个栗子,假设如下定义了一个时间类time,该类同时定义了拷贝构造函数移动构造函数

class time {
private:
    int hour;
    int minute;
    int second;

public:
    time(int h, int m, int s) :hour(h), minute(m), second(s) {
    }

    time(const time& t) :hour(t.hour), minute(t.minute), second(t.second) {
        cout << "copy" << endl;
    }

    time(const time&& t) noexcept:hour(t.hour),minute(t.minute),second(t.second) {
        cout << "move" << endl;
    }
};

main方法之中执行下面的代码逻辑:

int main()
{
    vector<time> tlist;
    time t(1, 2, 3);
    tlist.emplace_back(t);
    tlist.emplace_back(2, 3, 4);  //直接调用了time的构造函数在vector的内存之中建立起新的对象

    getchar();
}

执行结果:

  copy                    
  move (这次拷贝构造函数的调用是因为vector本身的扩容,也就是移动之前的已经容纳的time对象)

由上述代码我们看到time对象可以直接利用emplace_back方法在vector上构造对象,通过这样的方式来减少不必要的内存操作。(省去了拷贝构造的环节)。同样的在main之中执行下面的代码逻辑:

int main()
{
    vector<time> tlist;
    time t(1, 2, 3);
    tlist.emplace_back(move(t)); //调用move函数使time对象成为右值,可以利用移动构造函数来拷贝对象
    tlist.emplace_back(2, 3, 4);  //直接调用了time的构造函数在vector的内存之中建立起新的对象

    getchar();
}

执行结果:

  move                   
  move (这次拷贝构造函数的调用是因为vector本身的扩容,也就是移动之前的已经容纳的time对象)

通过这样的方式也减少不必要的内存操作。(省去了移动构造的环节)。所以这就是为什么在C++11之后提倡大家使用emplace_back来代替旧代码之中的push_back函数。如下面的代码所示,在push_back底层也是调用了emplace_back来实现对应的操作流程:

void push_back(const _Ty& _Val) {   
       emplace_back(_Val);
}

void push_back(_Ty&& _Val) {    
       emplace_back(_STD move(_Val));
}

2.emplace_back的实现

源码面前,了无秘密,接下来跟随笔者直接来看看emplace_back的源代码,来引出我们今天的主题:

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())
            {
            _Emplace_back_with_unused_capacity(_STD forward<_Valty>(_Val)...);
            }
        else
            {   // reallocate
            const size_type _Oldsize = size();

            if (_Oldsize == max_size())
                {
                _Xlength();
                }

            const size_type _Newsize = _Oldsize + 1;
            const size_type _Newcapacity = _Calculate_growth(_Newsize);
            bool _Emplaced = false;
            const pointer _Newvec = this->_Getal().allocate(_Newcapacity);
            _Alty& _Al = this->_Getal();

            _TRY_BEGIN
            _Alty_traits::construct(_Al, _Unfancy(_Newvec + _Oldsize), _STD forward<_Valty>(_Val)...);
            _Emplaced = true;
            _Umove_if_noexcept(this->_Myfirst(), this->_Mylast(), _Newvec);
            _CATCH_ALL
            if (_Emplaced)
                {
                _Alty_traits::destroy(_Al, _Unfancy(_Newvec + _Oldsize));
                }

            _Al.deallocate(_Newvec, _Newcapacity);
            _RERAISE;
            _CATCH_END

            _Change_array(_Newvec, _Newsize, _Newcapacity);
            }

#if _HAS_CXX17
        return (this->_Mylast()[-1]);
#endif /* _HAS_CXX17 */
        }

通过上述代码可以看到,emplace_back的流程逻辑很简单。先检查vector的容量,不够的话就扩容,之后便通过_Alty_traits::construct来创建对象。而最终利用强制类似装换的指针来指向容器类之中对应类的构造函数,并且利用可变长模板将构造函数所需要的内容传递过去构造新的对象。

template<class _Objty,
        class... _Types>
        static void construct(_Alloc&, _Objty * const _Ptr, _Types&&... _Args)
        {   // construct _Objty(_Types...) at _Ptr
        ::new (const_cast<void *>(static_cast<const volatile void *>(_Ptr)))
            _Objty(_STD forward<_Types>(_Args)...);
        }

emplace_back这里最为巧妙的部分就是利用可变长模板实现了,任意传参的对象构造。可变长模板是C++11新引进的特性,接下来我们来详细看看可变长模板是如何来使用,来实现任意长度的参数呢?

3.可变长模板与函数式编程

首先,我们先看看,可变长模板的定义:

    template <class... T>
    void f(T... args);

通过template来声明参数包args,这个参数包中可以包含0到任意个参数,并且作为函数参数调用。之后我们便可以在函数之中将参数包展开成一个一个独立的参数。

假设我们有如下需求,需要定义一个max_num函数来求出一组任意参数数字的最大值,在C++11之前的版本或许需要这样去定义这个函数,也就是说我们需要一个参数来指定对应参数的个数,并且这个过程之中存在参数的类型不一致的潜在风险,并不能在编译期进行反馈(不能在编译期进行对于动态语言来说根本不是什么大不了的问题,囧rz):

int max_num(int count, ...)
{
    va_list ap;
    va_start(ap, count);

    int ans = va_arg(ap, int);
    for (int i = 1; i < count; ++i)
    {
        int num = va_arg(ap, int);
        ans = max(ans, num);
    }

    va_end(ap);

    return ans;
}

而利用可变长模板,我们可以很优雅地通过以下的代码来实现一个这样的函数:

template<typename t1,typename ...t2> t1 max_num(t1 num, t2 ...args) {
    auto n = max_num(args...);
    return n > num ? n : num;
}
template<typename t1> t1 max_num(t1 num) {
    return num;
}

通过不断递归的方式,提取可变长模板参数之中的首个元素,并且设置递归的终止点的方式来依次处理各个元素。这种处理函数的方式本质上就是在通过递归的方式处理列表,这种编程思路在函数式编程语言之中十分常见,在C++之中看到这样的用法,也让笔者作为C++的入门选手感到很新奇。笔者曾经接触过Scala与Erlang语言之中大量利用了这种写法,但是多层递归导致的必然是栈调用的开销变大,利用尾递归的方式来优化这样的写法,才能减少非必要的函数调用开销。

4.小结

由emplace_back引申出来不少对C++11新特性的探索,笔者也仅仅做一些抛砖引玉的工作。作为程序员,希望大家能够坚持不断动态更新对语言的学习与探索来凝练与高效率的Coding,这也是笔者坚持更新该系列文章的初衷。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • 前言 把《C++ Primer》[https://book.douban.com/subject/25708312...
    尤汐Yogy阅读 9,504评论 1 51
  • 你有没有一个无法靠近的人? 为了靠近他,你想尽办法,用尽心思,最后只得到一个过分热情的结论,你毫无反抗之力,也无从...
    花田夜语阅读 479评论 0 1
  • 每一次归故乡总是近乡情怯,走在熟悉的街道,空气中弥漫着熟悉的花香,我知道不管走多远,不管长多大,不管生活有多糟,不...
    十一幺儿阅读 144评论 0 0
  • 一夜秋风紧,寒蛩不住鸣。 大江流暗响,群雁叫漆空。 捣练谁家妇,织梭那壁声。 更深恼雨至,归梦总难成。 注:海棠短...
    庆善阅读 348评论 2 9