【问题标题】:C++ universal reference in constructor and return value optimization (rvo)构造函数中的 C++ 通用引用和返回值优化 (rvo)
【发布时间】:2014-07-24 04:27:32
【问题描述】:

为什么在具有通用引用参数的构造函数的类中不会发生右值优化?

http://coliru.stacked-crooked.com/a/672f10c129fe29a0

#include <iostream>

 template<class ...ArgsIn>
struct C {

  template<class ...Args>
  C(Args&& ... args) {std::cout << "Ctr\n";}        // rvo occurs without &&

  ~C(){std::cout << "Dstr\n";}
};

template<class ...Args> 
auto f(Args ... args) {
    int i = 1;
  return C<>(i, i, i);
}

int main() {
  auto obj = f();
}

输出:

Ctr
Ctr
Dstr
Ctr
Dstr
Dstr

【问题讨论】:

  • Universal reference 是一个特定作者创造的术语,但其他人不同意……我个人认为它造成的混乱多于帮助,您应该尝试了解如何类型推断有效,因为这是这里问题的答案。
  • 使用 g++ 4.8.3 我得到 Ctr Dstr 对于 C(Args... args)C(Args &amp;&amp;... args) 。你的编译器和版本(和标志)是什么?
  • @MattMcNabb "g++ -std=c++1y -O3 -Winline -Wextra -pthread -pedantic-errors" 4.9 版。 GCC 4.8.1 也不做 rvo coliru.stacked-crooked.com/a/d2ddb81f9ed2d217
  • @MattMcNabb: auto f() { 是(将是)合法的 C++14,一些编译器已经支持它。
  • @DavidRodriguez 为移动构造函数启用cout,我仍然只得到Ctr Dstr 两个版本。 (添加-fno-elide-constructors得到CtrmoveDstrmoveDstrDstr

标签: c++ templates rvalue-reference rvo universal-reference


【解决方案1】:

我认为问题在于

template<class ...Args>
C(Args&& ... args) {std::cout << "Ctr\n";}  

就语言而言不是复制/移动构造函数,因此编译器不能省略对它们的调用。从 §12.8 [class.copy]/p2-3 开始,添加了重点并省略了示例:

X非模板构造函数是一个拷贝构造函数,如果 它的第一个参数是X&amp;const X&amp;volatile X&amp;const volatile X&amp;类型, 要么没有其他参数,要么全部 其他参数有默认参数 (8.3.6)。

X非模板构造函数是一个移动构造函数,如果 它的第一个参数是X&amp;&amp;const X&amp;&amp;volatile X&amp;&amp;const volatile X&amp;&amp;,要么没有其他参数,要么全部 其他参数有默认参数 (8.3.6)。

换句话说,作为模板的构造函数永远不能是复制或移动构造函数。

返回值优化是复制省略的一种特殊情况,描述为(§12.8 [class.copy]/p31):

当满足某些条件时,允许省略实现 类对象的复制/移动构造,即使构造函数 选择用于复制/移动操作和/或析构函数 对象有副作用。

这允许实现省略“复制/移动构造”;使用既不是复制构造函数也不是移动构造函数的东西构造对象不是“复制/移动构造”。

因为C 有一个用户定义的析构函数,所以不会生成隐式移动构造函数。因此,重载决策将选择 Args 推导出为 C 的模板化构造函数,这比右值的隐式复制构造函数更匹配。但是,编译器不能省略对这个构造函数的调用,因为它有副作用,既不是复制构造函数也不是移动构造函数。

如果是模板化的构造函数

template<class ...Args>
C(Args ... args) {std::cout << "Ctr\n";} 

那么它不能用Args = C 实例化来产生一个复制构造函数,因为这会导致无限递归。标准中有一条特殊规则禁止此类构造函数和实例化(§12.8 [class.copy]/p6):

X 的构造函数声明是非良构的,如果它 第一个参数的类型(可选 cv 限定)X 和 没有其他参数,否则所有其他参数都有 默认参数。成员函数模板永远不会被实例化为 生成这样的构造函数签名。

因此,在这种情况下,唯一可行的构造函数将是隐式定义的复制构造函数,并且可以省略对该构造函数的调用。

如果我们改为 remove 来自 C 的自定义析构函数,并添加另一个类来跟踪何时调用 C 的析构函数:

struct D {
    ~D() { std::cout << "D's Dstr\n"; }
};

template<class ...ArgsIn>
struct C {
  template<class ...Args>
  C(Args&& ... args) {std::cout << "Ctr\n";}
  D d;
};

我们只看到对D 的析构函数的一次调用,表明只构造了一个C 对象。这里C的移动构造函数是由重载决议隐式生成和选择的,你会看到RVO再次启动。

【讨论】:

  • 你能解释一下这与问题有什么关系吗?
  • @MattMcNabb RVO 省略了复制/移动构造函数调用。它不会(显然)忽略对两者都不是的东西的调用。
  • @tower120 不,这不是编译器错误。这是标准规定的行为。编译器不允许省略对模板化构造函数的调用,因为它既不是复制构造函数也不是移动构造函数。
  • @tower120 返回值优化复制省略。这就是它的指定方式——允许编译器省略某些复制/移动操作。
  • 那么你完全理解错了。声明复制和移动构造函数,您将在所有允许的情况下获得 RVO,或者使用 SFINAE 约束构造函数模板,使其不能用于复制或移动 C 对象。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-01-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-12-04
相关资源
最近更新 更多