【问题标题】:Initialize rvalue reference member初始化右值引用成员
【发布时间】:2020-01-19 21:41:11
【问题描述】:

我试图在以下情况下初始化右值引用成员:struct A 是一个聚合,class B 有一个用户定义的构造函数,因此它不是聚合。根据cppreference这里,

struct A {
  int&& r;
};
A a1{7}; // OK, lifetime is extended
A a2(7); // well-formed, but dangling reference

我的struct A 中的引用应该被正确初始化并且临时字符串应该被扩展,但事实并非如此。
对于我的class B,在同一个 cppreference 页面中:

绑定到构造函数初始化列表中的引用成员的临时绑定仅持续到构造函数退出,而不是只要对象存在。 (注意:从 DR 1696 开始,此类初始化格式不正确)。 (直到 C++14)

但我仍然遇到/std:c++latest 的 MSVC 问题。我错过了什么吗?

struct A
{
    std::string&& ref;
};

class B
{
    std::string&& ref;
public:
    B(std::string&& rref):ref(std::move(rref)){}
    void print() {std::cout<<ref<<'\n';}
};

int main()
{
    A a{ std::string{"hello world"} };
    std::cout<<a.ref<<'\n'; //garbage in MSVC with c++latest, correct in GCC9.2
    B b{ std::string{"hello world"} };
    b.print(); //same
}

编辑:请告诉我,如果我首先在这两种情况下得到悬空参考。而对于 MSVC,版本是最新更新,v19.24.28315

EDIT2:好的,我实际上对 cppreference 的解释感到困惑。我并不是专门要求 C++20。请告诉我在哪个 C++ 版本下,代码格式是否正确。

EDIT3:是否有适当的方法来初始化非聚合类的右值引用成员?由于将其绑定到成员初始化器列表中的临时值是错误的(直到 C++14,这是否意味着它自 C++14 以来就很好?)并且将临时值传递给期望右值的构造函数不能将其生命周期延长两次.

【问题讨论】:

  • 引用不支持你的论点,即A a1{7}; 延长了临时的寿命。
  • /std:c++latest 没有指定 C++ 标准 (这在这个问题中可能非常相关)。 最新 c++ 版本取决于 MSVC 版本。如果能指定C++版本可能会更好
  • Fwiw,应该避免引用成员。这对于右值引用更是如此,因为当它们是类成员时,它们不会延长临时对象的生命周期
  • @HolyBlackCat 这是新的 C++20 括号聚合初始化语法。 GCC 中继已经支持它:godbolt.org/z/K-4CXL
  • @NathanOliver 您能否为右值引用提供参考,当它们是类成员时,它们不会延长临时对象的生命周期?

标签: c++


【解决方案1】:

您的类 A 是一个聚合类型,但 B 不是,因为它有一个用户提供的构造函数和一个私有的非静态成员。

因此,A a{ std::string{"hello world"} }; 是聚合初始化,确实通过引用绑定将临时对象的生命周期延长到 a 的生命周期。

另一方面,B 不是聚合初始化。它调用用户提供的构造函数,该构造函数传递引用。传递引用不会延长临时对象的生命周期。当B 的构造函数退出时,std::string 对象将被销毁。

因此,以后使用a 具有明确定义的行为,而使用b 将具有未定义的行为。

这适用于自 C++11 以来的所有 C++ 版本(在此之前程序显然格式错误)。

如果您的编译器正在为 a 打印垃圾(在从程序中删除 b 以使其不再具有未定义的行为之后),那么这是编译器中的错误。


关于问题的编辑:

无法通过绑定到非聚合类的引用成员来延长临时对象的生命周期。

完全依赖此生命周期扩展是非常危险的,因为如果您将来碰巧将类设为非聚合,您可能不会收到任何错误或警告。

如果您希望类始终保留对象直到其生命周期结束,那么只需按值存储它而不是按引用:

class B
{
    std::string str;
public:
    B(std::string str) : str(std::move(str)) {}
    void print() { std::cout << str << '\n'; }
};

【讨论】:

  • 1.删除b 后,MSVC 仍然打印垃圾。 (实际上它正在打印一个空行。)无论是default 还是 c++14 17 或最新版本。 2. 为什么初始化b 并没有延长临时的寿命? 3. 用B():ref(std::string{"hello world"}){}默认初始化b怎么样?
  • @szppeter 在b 的初始化中,临时对象首先绑定到构造函数参数中的引用,这会“延长”它的生命周期,直到包含构造函数调用的完整表达式结束。但是绑定另一个临时引用并不会进一步延长生命周期。使用B():ref(std::string{"hello world"}){} 进行初始化是您问题中引用的内容。自 C++14 以来,它的格式不正确,并且在此之前没有将生命周期延长到构造函数调用的持续时间之外。
  • 引用是until C++14,而不是since C++14
  • @szppeter 它是“直到 C++14”,因为由于 C++14 在成员初始化器列表中绑定对临时的引用是不正确的,所以谈论它是否延长临时寿命不再有意义。另请参阅en.cppreference.com/w/cpp/language/initializer_list
  • @walnut 很好的答案。我还可以问:如果aA 类型)是由临时的聚合初始化形成的,临时的生命周期是否仍然由堆栈控制?可以从函数中返回并使用a(的副本)吗?
猜你喜欢
  • 1970-01-01
  • 2015-03-08
  • 1970-01-01
  • 2015-04-27
  • 2019-09-10
  • 1970-01-01
  • 2020-12-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多