【问题标题】:std::vector::emplace_back with lvalue expression带有左值表达式的 std::vector::emplace_back
【发布时间】:2019-08-08 21:58:34
【问题描述】:

emplace_back 与某些结构S 的左值一起使用是否有意义:

像这样:

std::vector<S> v;
auto s = S(/*...*/);
v.emplace_back(s);

不仅仅是:

v.emplace_back(/* S constructor arguments */);

或者只是简单地滥用emplace_back,这只是因为const S&amp;(因此是一个复制构造函数)是Args... argsemplace_back中的合法实例化,并且没有明确禁止?

【问题讨论】:

  • 代码中是否需要s?如果没有,那就是滥用。
  • 你不会:v.emplace_back(std::move(s)); 否则你还不如使用push_back

标签: c++ variadic-templates emplace


【解决方案1】:

正如你已经说过的,传递const S&amp; 只会调用复制构造函数。

除非您打算在将s 传递给emplace_back 之前以某种方式使用它,否则它不一定是明智的。

但是,例如,如果创建 s 的代码特别长,则将它和 emplace_back 的代码放在不同的行上可以提高可读性。编译器非常擅长优化这种情况,并且可能无论如何都会生成相同的代码(如果复制构造函数是默认的)。基本示例:https://godbolt.org/z/D1FClE

如果它提高了可读性或可维护性,就这样做,否则它没有任何价值。

【讨论】:

  • std::move(s) 可能允许使用移动构造函数以避免复制。
  • @Jarod42 是的。但 std::move 将 s 转换为“xvalue”,问题是关于左值。
  • "如果复制构造函数是默认的"。起初我试图用一些打印出来的用户定义复制构造函数来查看它是否被调用,但我猜这实际上通过调用它影响了测试。但是,仅用于实际构造函数参数的带有参数包的 emplace_back 不会调用复制构造函数,即使它是用户定义的。
  • @Student4K 您的最后一句话描述了预期的行为。添加打印语句肯定会阻止编译器优化复制构造函数。
【解决方案2】:

如果后面的代码不需要s,那么这是对emplace_back()函数的误用。这是因为您正在调用 S 类的复制构造函数,而不是将参数传递给 emplace_back(),后者将使用来自 S 的正确构造函数。

考虑以下代码:

#include <iostream>
#include <vector>

struct S
{
    S()          {std::cout<< "     default ctor" <<std::endl;}
    S(int)       {std::cout<< "     user-def ctor" <<std::endl;}
    S(const S &) {std::cout<< "     copy ctor" <<std::endl;}
    S(S &&)      {std::cout<< "     move ctor" <<std::endl;}
};

int main()
{
    std::vector<S> v;
    v.reserve(5);

    std::cout<< "auto calls: " <<std::endl;
    auto s = S();
    std::cout<<std::endl;

    std::cout<< "emplace_back( s ) calls: " <<std::endl;
    v.emplace_back(s);
    std::cout<<std::endl;

    std::cout<< "emplace_back( std::move(s) ) calls: " <<std::endl;
    v.emplace_back(std::move(s));
    std::cout<<std::endl;

    std::cout<< "emplace_back( S{} ) calls: " <<std::endl;
    v.emplace_back(S{});
    std::cout<<std::endl;

    std::cout<< "emplace_back( ) calls: " <<std::endl;
    v.emplace_back();
    std::cout<<std::endl;

    std::cout<< "emplace_back( 2 ) calls: " <<std::endl;
    v.emplace_back(2);
    std::cout<<std::endl;
}

结果是:

auto calls: 
     default ctor

emplace_back( s ) calls: 
     copy ctor

emplace_back( std::move(s) ) calls: 
     move ctor

emplace_back( S{} ) calls: 
     default ctor
     move ctor

emplace_back( ) calls: 
     default ctor

emplace_back( 2 ) calls: 
     user-def ctor

预留空间用于为 5 个Ss 分配空间。在不保留空间的情况下,输出将包括从向量中对复制 ctor 的额外调用。

当您只是将参数传递给 S 的构造函数时(在这种情况下,什么都没有),emplace_back() 直接在向量内使用默认 ctor 创建一个 S 对象。

顺便说一句,在这些情况下,请查看您的朋友example in godbolt,以了解后台发生的确切情况。

【讨论】:

    猜你喜欢
    • 2021-11-18
    • 2017-01-12
    • 1970-01-01
    • 2015-01-15
    • 1970-01-01
    • 1970-01-01
    • 2017-09-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多