这里的确切操作顺序是什么?

时间:2016-09-22 08:18:03

标签: c++ c++11

我有一个功能:

std::string makeMeat() { return "Pork"; }

在代码中的某处我用这种方式:

std::string meat = makeMeat();

我想知道这行代码的确切操作顺序是什么。假设有两种不同的情况:

  1. std :: string没有移动构造函数(仅作为示例)
  2. std :: string具有移动构造函数
  3. 我猜makeMeat()创建了类std::string的临时对象。

    std::string temp("Pork");
    

    之后创建std::string meat对象并使用来自temp对象的数据的复制构造函数进行初始化?

    std::string meat(temp);
    

    最后temp对象被销毁?

    如果没有返回值优化,我认为会发生这种情况。 如果是这样会发生什么?

2 个答案:

答案 0 :(得分:3)

该字符串直接在meat中构建。没有具有不同生命的临时工。这被称为elision。

这种行为是在C ++ 17下强制执行的,并且在实践中发生在任何合理的现代生产质量的现代编译器中,在C ++ 03 11和14中没有设置病态构建标志。

在C ++ 14及更早版本中,类必须有一个移动或复制ctor才能实现上述功能,或者你得到一个构建中断。所述构造函数中的代码都不会运行。

古代或玩具编译器,或带有病态标志的编辑器告诉他们不要忽视,可能最多可以制作2个临时对象并乱用副本。这种情况并不重要,因为病态编译器状态同样可以自由地实现a+=b;ab整数类型)for (i from 0 to b)++a;!你应该诚实地认为缺乏同样的病态。

C ++中的Elision指的是标准允许的对象生存期和身份的合并。所以在某种意义上存在3个字符串(函数的临时eithin,返回值和从返回值构造的值),它们的身份被合并到一个具有统一生命期的对象中。

答案 1 :(得分:2)

您可以使用自定义结构对此进行测试:

struct S {
    S (const char *);
    S (S const&) = default;
    S (S&&) = default;
    virtual ~S();
};

S get_s () { return "S"; }

int main () {
    S s = get_s();
}

如果没有选项,g ++将忽略大多数构造函数调用,此代码等同于:

S s("S");

因此只调用const char *中的构造函数。

现在,如果你告诉g ++不要忽略构造函数(-fno-elide-constructors),那么有三个构造函数/析构函数调用:

  1. 第一个创建临时S("S");
  2. 第二个创建临时内部get_sS(S&&);
  3. 然后调用第一个临时的析构函数;
  4. 然后在main;
  5. 内调用移动构造函数
  6. 然后调用get_s返回的临时表的析构函数;
  7. 然后调用s的析构函数。
  8. 如果S没有移动构造函数,则可以简单地用上面列表中的复制构造函数替换移动构造函数。