是否可以初始化不可复制类型的成员变量(或基类)

Is it possible to initialize member variable (or base class) of a non-copyable type?

本文关键字:变量 成员 基类 类型 初始化 可复制 是否      更新时间:2023-10-16

考虑以下代码:

struct S
{
S() = default;
S(S const&) = delete;
// S(S&&) = delete;  // <--- uncomment for a mind-blowing effect:
// MSVC starts compiling EVERY case O_O
};
S foo() { return {}; }
struct X : S
{
//    X() : S(foo()) {}   // <----- all compilers fail here
};
struct Y
{
S s;
Y() : s(foo()) {}   // <----- only MSVC fails here
};
struct Z
{
S s = {};           // ... and yet this is fine with every compiler
Z() {}
};
//S s1(foo());      // <-- only MSVC fails here
//S s2 = foo();     // <-- only MSVC fails here

问题:

  • 看起来没有办法用prvalue初始化不可复制的基类——这是正确的吗?看起来标准中有缺陷(或者我尝试的所有编译器都不兼容(

  • MSVC无法初始化成员变量——这是否意味着它不符合要求?有办法解决这个问题吗?

  • 为什么添加S(S&&) = delete;会导致MSVC在每个情况下编译

因此,我认为我找到了标准的相关部分,并且我认为编译器在X方面存在错误。(所有链接都指向一个标准草案,所以在C++17中可能会有所不同,我稍后会检查。但gcc10和clang10在-std=c++20中也会失败,所以这并不重要(。

关于基类的初始化(emphasis mine(:class.base.init/7

mem初始化器中的表达式列表或braked init列表用于根据[dcl.init]的初始化规则初始化指定的子对象(或者,在委托构造函数的情况下,是完整的类对象(,以进行直接初始化

我认为这告诉我们,X() : S(foo()) {}应该与S s = foo()没有什么不同,但让我们看看dcl.init/17.6.1

如果初始值设定项表达式是一个prvalue,并且源类型的cv不合格版本与目标的类是同一类,则初始值设定值表达式用于初始化目标对象。[示例:T x = T(T(T()));调用T默认构造函数初始化x。--结束示例]

这意味着X() : S(foo()) {}应该调用默认构造函数。我还测试了(与示例完全一致(X() : S(S()) {},这在clang和g++上也失败了。所以在我看来,编译器有一个缺陷。

看起来没有办法用prvalue初始化不可复制的基类——这是正确的吗?看起来标准中有缺陷(或者我尝试的所有编译器都不兼容(

标准说它应该有效。这个标准是错误的。

基类子对象(更常见的是,潜在重叠的子对象(可能具有与相同类型的完整对象不同的布局,或者其填充可以被其他对象重用。因此,不可能从返回prvalue的函数中删除副本或移动,因为该函数不知道它没有初始化一个完整的对象。

剩下的都是微软风投的bug。

不,这是不允许的,但自从c++17以来,有了一些新功能,其中一个功能不再是copy object

函数返回prvalues不再复制对象(mandatory copy elision(,并且有一个新的prvalue到glvalue的转换称为临时物化转换。此更改意味着复制省略现在有保证,并且甚至适用于不可复制或可移动的类型。这允许您定义返回这样的类型。

有保证的拷贝省略C++17


下面的函数,从不返回S()对象并返回std::initialization_list{},而是作为S s ={},是一个有效的转换,因此基于副本省略优化不是要返回副本,粗略地说-直接返回std::initialization_list本身。注意临时物化转换

S foo() { return {}; }

以下不起作用,

S foo() { S s = {}; return s; }

因此,行Y() : s(foo()) {}粗略地说-现在可以被解释为隐式类型转换

S s = {}