emplace_back与push_back的原始类型

时间:2018-01-08 21:26:19

标签: c++ c++11 vector

我想知道emplace_back的{​​{1}}和push_back方法在使用原始标量类型时是否有任何不同,例如std::vector或{{1} }。直观地说,我猜想,在编译之后,两种变体都会在这里产生相同的字节码:

std::uint32_t

请纠正我,如果那已经错了......

现在,我开始挣扎的地方是,当我问自己,如果"列表的类型会发生什么?和矢量不匹配:

std::uint8_t

void copyListContent(std::uint8_t * list, std::size_t nElems, std::vector<std::uint8_t> & vec) { vec.clear(); vec.reserve(nElems); for (std::size_t i = 0; i < nElems; ++i) { //variant 1: vec.push_back(list[i]); //variant 2: vec.emplace_back(list[i]); } } 元素在将它们放入向量时会转换为void copyListContent(std::uint8_t * list, std::size_t nElems, std::vector<std::uint32_t> & vec) { //... same code as above } (使用std::uint8_tstd::uint32_t),所以我想知道是否会触发一些&#34;构造函数&#34;被称为?在这种情况下,emplace_back会更有效率,因为它会避免构造一个可以复制的临时对象吗?或者这些隐含的转化不会产生任何影响,push_backemplace_back的行为会相同吗?

所以,我问自己,你: 对于像这样的原始类型,emplace_backpush_back的行为总是相似吗?

作为一个模糊的猜测我可能会说&#34;可能是的&#34;,但我对C ++内部的知识不足以为我自己可靠地回答这个问题。在这种情况下,我很乐意了解事情是如何运作的 - 非常感谢!

2 个答案:

答案 0 :(得分:4)

GCC将两个版本的代码编译为相同的结果程序集(Godbolt.org):

#include<vector>

void push(std::vector<int> & vec, int val) {
    vec.push_back(val);
}

vs

#include<vector>

void push(std::vector<int> & vec, int val) {
    vec.emplace_back(val);
}

两者都会导致以下程序集:

push(std::vector<int, std::allocator<int> >&, int):
        push    r15
        push    r14
        push    r13
        push    r12
        push    rbp
        push    rbx
        sub     rsp, 24
        mov     rbx, QWORD PTR [rdi+8]
        cmp     rbx, QWORD PTR [rdi+16]
        je      .L2
        mov     DWORD PTR [rbx], esi
        add     rbx, 4
        mov     QWORD PTR [rdi+8], rbx
        add     rsp, 24
        pop     rbx
        pop     rbp
        pop     r12
        pop     r13
        pop     r14
        pop     r15
        ret
.L2:
        mov     r12, QWORD PTR [rdi]
        mov     r14, rbx
        mov     ecx, esi
        mov     rbp, rdi
        sub     r14, r12
        mov     rax, r14
        sar     rax, 2
        je      .L9
        lea     rdx, [rax+rax]
        mov     r15, -4
        cmp     rax, rdx
        ja      .L4
        movabs  rsi, 4611686018427387903
        cmp     rdx, rsi
        jbe     .L19
.L4:
        mov     rdi, r15
        mov     DWORD PTR [rsp], ecx
        call    operator new(unsigned long)
        mov     ecx, DWORD PTR [rsp]
        mov     r13, rax
        add     r15, rax
.L5:
        lea     rax, [r13+4+r14]
        mov     DWORD PTR [r13+0+r14], ecx
        mov     QWORD PTR [rsp], rax
        cmp     rbx, r12
        je      .L6
        mov     rdx, r14
        mov     rsi, r12
        mov     rdi, r13
        call    memmove
.L7:
        mov     rdi, r12
        call    operator delete(void*)
.L8:
        mov     QWORD PTR [rsp+8], r13
        movq    xmm0, QWORD PTR [rsp+8]
        mov     QWORD PTR [rbp+16], r15
        movhps  xmm0, QWORD PTR [rsp]
        movups  XMMWORD PTR [rbp+0], xmm0
        add     rsp, 24
        pop     rbx
        pop     rbp
        pop     r12
        pop     r13
        pop     r14
        pop     r15
        ret
.L6:
        test    r12, r12
        je      .L8
        jmp     .L7
.L9:
        mov     r15d, 4
        jmp     .L4
.L19:
        xor     r15d, r15d
        xor     r13d, r13d
        test    rdx, rdx
        je      .L5
        lea     r15, [0+rax*8]
        jmp     .L4

正如您可能已经推断的那样,在处理具有更复杂的构造/复制/移动行为的类型时,这不是您可以依赖的行为,但对于原始类型,差异可以忽略不计。

话虽如此,有一种可能存在差异的情况:

std::vector<int16_t> vec;
size_t seed = 0x123456789abcdef;

vec.push_back(seed);

VS

vec.emplace_back(seed);

在(经过适当优化的)编译器中,两个汇编代码可能完全相同,但是您将从编译器获得不同的缩小警告(或者如果强制警告导致编译失败,则会出现错误)。后者更有可能提供难以诊断的警告消息,因为错误将来自内部<vector>而不是内部任何.cpp文件进行调用。

答案 1 :(得分:1)

Abseil网站上发布的Google指南:https://abseil.io/tips/112是您更喜欢使用push_back,因为它更具可读性。

担心内置类型的隐式转换似乎过早优化;很有可能你的编译器会优化转换。