【问题标题】:Return value optimization in Visual Studio 2015?Visual Studio 2015 中的返回值优化?
【发布时间】:2015-10-10 22:32:33
【问题描述】:

在一个 probject 中,我使用了类似于以下的代码:

class C {
public:
    C() {}

    C(const C&) = delete;
};

C f() {
    return C();
}

int main() {
    f();
}

在我使用过的所有 Visual C++ 编译器(截至 2013 年)中,这从来都不是问题。但是当我尝试使用新的 Visual C++ 2015 编译器编译它时,我收到以下错误:

1>c:\devel\c++11\playground\main.cpp(10): error C2280: 'C::C(const C &)': attempting to reference a deleted function
1>  c:\devel\c++11\playground\main.cpp(6): note: see declaration of 'C::C'

我不确定它以前为什么会起作用,但我认为由于返回值优化,调用了默认构造函数而不是复制构造函数。

我使用的代码甚至是合法的 C++ 吗?如果不是,那么在不需要我的类C 的复制构造函数的情况下实现此代码的正确方法是什么?我当然可以使用移动构造函数,但我假设代码在 C++11 之前永远不会是有效的 C++?

【问题讨论】:

  • 如果我没记错的话,VS 直到 VS2013 才实现 `=delete;` 语法,所以当你说“在我使用的所有以前的 Visual C++ 编译器中(直到 2013 年),从来没有一个问题”,你的意思是“在 VS2013 中,这不是问题”?
  • 在VS2013之前,我只是使用了复制构造函数声明,没有实现,如果实际使用会导致链接器错误。

标签: c++ visual-studio visual-c++ visual-studio-2015


【解决方案1】:

函数参数和返回值使用copy-initialization 初始化。 复制初始化要求复制构造函数是可访问的,即使它们被 (N)RVO 省略:

如果 T 是类类型,而 other 的类型不同,或者如果 T 是非类类型,但 other 的类型是类类型,用户定义的转换序列可以从 other 的类型转换到 T(或从 T 派生的类型,如果 T 是类类型并且转换函数可用)进行检查,并通过重载决议选择最佳的。转换的结果,如果使用了转换构造函数,它是一个临时纯右值,然后用于直接初始化对象。 最后一步通常是优化出来的,转换的结果直接在为目标对象分配的内存中构造,但需要适当的构造函数(移动或复制)即使不使用也可以访问。强>

【讨论】:

  • 但这是否意味着该行为在以前版本的 Visual Studio 中总是非法的,因为没有复制构造函数?
  • Visual Studio 在遵循 C++ 标准方面从未堪称典范。
  • 这正是正在发生的事情。编译器要求复制构造函数可用,但实际上从未调用它。
  • @fschoenm 不,以前的行为是完全合法的——你声明了复制构造函数,所以它是可访问的:使用它的 return 语句不会导致错误。由于编译器应用了 RVO,因此实际上并未调用构造函数,因此您没有提供实现这一事实并不重要。现在您正在声明副本构造 = 删除,它不再可访问,因此出现错误。
  • 哦,这一点也不令人困惑。 :/ 如果它被省略,那么它不应该导致错误,否则这可能会引入意想不到的错误。
【解决方案2】:

您需要关注rule of 5。由于您删除了复制构造函数,因此您需要定义移动构造函数。

C(C&&) = default;
C& operator=(C&&) = default;

With move constructor - 有效
Without move constructor - 无效,违反规则 5

请注意,上面的站点使用 gcc,即使它也不会编译,所以它不是特定于 Visual Studio,这是一个已定义和预期的行为。

【讨论】:

  • 这解释了如何修复它,但不是为什么它首先发生。这可能与以前版本的 VS 为 unable to auto-generate the implicitly declared move-constructor and assignment 的事实有关。尽管如此,如果没有复制构造函数,为什么代码首先会编译?
  • 此代码在以前的版本中无法编译。在 VS2013 之前,delete 关键字不存在(to delete a function,不是删除指针),所以它不会被编译。并且在 VS2013 中,由于我描述的原因,它会抱怨尝试使用已删除的功能。
  • @CoryKramer 这并不完全正确。我可以拿我在问题中发布的示例并在 VS2013 中编译它,而它在 VS2015 中产生编译错误。
  • @fschoenm 在这种情况下,如果 VS2013 允许,我会将我的评论从“它不会不编译”修改为“它应该不编译”要编译的 OPs 代码是错误的并且不符合 C++11 标准。
【解决方案3】:

也许以前它设法为此选择隐式生成的移动构造函数,但在 VS 2015 中,移动构造函数的隐式生成被复制操作的存在所阻止,如果我没记错的话,这是符合标准的行为。

所以您只需要自己定义移动构造函数,可能是 =default 一个。

【讨论】:

  • "以前它设法为此选择隐式生成的移动构造函数" 实际上在 VS2013 中根本没有隐式生成的移动构造函数(它们是在这方面不符合 C++11 标准)。他们最终添加了 VS2015 的功能。
  • 是否在 2013 CTP 中添加了隐式生成的移动操作(以及对它们的处理 =default)?如果它是在没有与复制操作的poper合作的情况下添加的,那么如果原始发布者使用这个编译器版本,它可以解释行为的变化
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多