【问题标题】:Are there any cases where it is incorrect to replace push_back with emplace_back?是否存在将 push_back 替换为 emplace_back 不正确的情况?
【发布时间】:2014-03-31 13:51:27
【问题描述】:

我可以通过将std::vector::push_back 替换为emplace_back 并使用C++ 11 编译器编译它来破坏有效的C++03 程序吗?通过阅读emplace_back 参考,我认为这不应该发生,但我承认我没有完全得到右值参考。

【问题讨论】:

    标签: c++ c++11 vector c++03


    【解决方案1】:

    我构造了一个简短的示例,当push_backemplace_back 替换时实际上无法编译:

    #include <vector>
    struct S {
        S(double) {}
      private:
        explicit S(int) {}
    };
    int main() {
        std::vector<S>().push_back(0); // OK
        std::vector<S>().emplace_back(0); // error!
    }
    

    push_back 的调用需要将其参数0 从类型int 转换为类型S。由于这是一个隐式转换,所以不考虑显式构造函数S::S(int),而是调用S::S(double)。另一方面,emplace_back 执行直接初始化,因此S::S(double)S::S(int) 都被考虑在内。后者是更好的匹配,但它是private,所以程序格式不正确。

    【讨论】:

    • 确实,聪明。当我第一次看到这个问题时,我想,“好吧,emplace_back 可以调用显式构造函数”。然后我愚蠢地想,“但是当从emplace_back 移动到push_back 时,这是一个突破性的变化,而不是相反”。您可以在emplace_back 调用变得模棱两可而push_back 不是struct S { S(double) {} explicit S(float) {} }; 的情况下做同样的事情,但当然这是一组可怕 构造函数给S
    • 那是不是说只要代码能编译成功就不用担心转emplace_back了?
    【解决方案2】:

    是的,您可以更改行为(不仅仅是避免复制构造函数调用),因为emplace_back 只能看到不完美的转发参数。

    #include <iostream>
    #include <vector>
    using namespace std;
    
    struct Arg { Arg( int ) {} };
    
    struct S
    {
        S( Arg ) { cout << "S(int)" << endl; }
        S( void* ) { cout << "S(void*)" << endl; }
    };
    
    auto main()
        -> int
    {
        vector<S>().ADD( 0 );
    }
    

    示例构建:

    [H:\开发\测试\0011] > g++ foo.cpp -D ADD=emplace_back && a S(int) [H:\开发\测试\0011] > g++ foo.cpp -D ADD=push_back && a S(void*) [H:\开发\测试\0011] > _

    附录:正如 Brian Bi 在 his answer 中指出的那样,另一个可能导致不同行为的区别是 push_back 调用涉及到 T 的隐式转换,这忽略了 @ 987654326@ 构造函数和转换运算符,而emplace_back 使用直接初始化,它也考虑了explicit 构造函数和转换运算符。

    【讨论】:

    • 您能否更详细地解释一下这是如何工作的?我试图自己弄清楚,但我真的很感兴趣。
    • @BrianBi:一个转换序列中不能有多个用户定义的转换。所以null pointer constant -> void* -> Spush_back 唯一可用的转换。对于emplace_back0 是一个空指针常量的事实丢失了,因为模板参数包无法捕获它,它只是捕获它是一个int(因此“不完美地转发”)。此外,不需要在一个序列中进行两个用户定义的转换,因为我们明确地构造了一个S。所以Arg构造函数被选择为S,隐式转换int -> Arg
    • @SteveJessop:谢谢,事实证明,在阅读您对 Brian 的回复之前,我并没有 100% 理解我自己的示例代码。 :-)
    【解决方案3】:

    emplace 版本在异常情况下根本不会创建所需类型的对象。这可能会导致错误。

    考虑以下示例,为简单起见,它使用std::vector(假设uptr 的行为类似于std::unique_ptr,除了构造函数不明确):

    std::vector<uptr<T>> vec;
    vec.push_back(new T());
    

    它是异常安全的。创建一个临时的uptr&lt;T&gt; 以传递给push_back,该push_back 被移动到向量中。如果向量的重新分配失败,分配的T 仍归智能指针所有,智能指针会正确删除它。

    比较:

    std::vector<uptr<T>> vec;
    vec.emplace_back(new T());
    

    emplace_back 不允许创建临时对象。 ptr 将在向量中就地创建一次。如果重新分配失败,就没有就地创建的位置,也永远不会创建智能指针。 T 将被泄露。

    当然,最好的选择是:

    std::vector<std::unique_ptr<T>> vec;
    vec.push_back(make_unique<T>());
    

    这相当于第一个,但使智能指针的创建显式。

    【讨论】:

      【解决方案4】:

      如果您在向量中保存的对象的复制构造函数中没有疯狂的副作用,那么没有。

      emplace_back 被引入以优化不必要的复制和移动。

      【讨论】:

      • 另请注意,其中一些优化应用于push_back
      • 是的,但是编译器必须确保构造函数是微不足道的,这样做不会改变整体结果
      • 我在考虑通过移动捕获,push_back 现在可以感知移动。不知道你在想我的意思。
      • 我的意思是编译器不能替换副本,这是使用 push_back 和 move 完成的,除非没有副作用。还是我误会了什么?
      • 这个答案在技术上是错误的(我的答案中给出了一个例子),但作为一个实践问题,你必须被先生困扰。墨菲为了破解密码。
      【解决方案5】:

      假设一个用户定义的类可以从花括号初始化器中初始化。例如

      struct S {
          int value;
      };
      

      然后

      std::vector<S> v;
      
      v.push_back({0});    // fine
      v.emplace_back({0}); // template type deduction fails
      

      std::vector::emplace_back 是模板函数,但 std::vector::push_back 是非模板函数。使用大括号初始化器 std::vector::emplace_back 会失败,因为 template argument deduction 失败。

      非推断上下文

      6) 参数 P,其 A 是一个花括号初始化列表,但 P 不是std::initializer_list 或对一个的引用:

      LIVE

      【讨论】:

        【解决方案6】:
        int main() {
        std::vector<S>().push_back(0);
        std::vector<S>().emplace_back(0); 
        }
        

        在 emplace_back 中给出 struct 构造函数,即上面的代码会是这样的

        int main() {
        std::vector<S>().push_back(0); 
        std::vector<S>().emplace_back(S(0)); 
        }
        

        【讨论】:

          猜你喜欢
          • 2011-05-17
          • 2012-06-09
          • 2014-07-06
          • 1970-01-01
          • 2018-11-15
          • 1970-01-01
          • 1970-01-01
          • 2016-11-30
          相关资源
          最近更新 更多