【问题标题】:Does copying an empty object involve accessing it复制空对象是否涉及访问它
【发布时间】:2018-06-28 17:47:17
【问题描述】:

灵感来自this 问题。

struct E {};
E e;
E f(e);  // Accesses e?

access是致

读取或修改对象的值

空类有一个implicitly defined copy constructor

非联合类X 的隐式定义的复制/移动构造函数执行其基类和成员的成员复制/移动。 [...] 初始化顺序与用户定义的构造函数中基和成员的初始化顺序相同。让x 是构造函数的参数,或者对于移动构造函数,是引用参数的xvalue。每个基本或非静态数据成员都以适合其类型的方式复制/移动:

  • [...] 基址或成员直接使用x 的相应基址或成员进行初始化。

【问题讨论】:

  • @YSC *pe 的评价本身就是UB,所以没有。
  • 假设sizeof(E) 是1;我希望复制 1 个字节。即使它完全未使用(这将被视为读取)
  • @UKMonkey 但是复制构造函数看起来像是定义为什么都不做,单个字节可能被认为是填充
  • @Quentin 不,不是。我们(语言律师)进行了讨论。但是,将引用绑定到空左值是未定义的,当然。
  • @Columbo 是吗?有没有我可以关注的链接?

标签: c++ language-lawyer


【解决方案1】:

我认为最准确地描述执行访问的标准部分是 [basic.life]。在本段中,解释了使用引用或指向超出其生命周期的对象的指针可以做什么。授权对此类实体进行的所有操作都不会访问对象值,因为此类值不存在(否则标准将不一致)。

所以我们可以举一个更激烈的例子,如果这不是未定义的行为,那么在你的示例代码中就无法访问e(根据上面的推理):

struct E{
     E()=default;
     E(const E&){}
     };
E e;
e.~E();
E f(e);

这里的e 是一个生命周期已经结束但其存储空间仍在分配的对象。 [basic.life]/6

中描述了使用这样的左值可以做什么

类似地,在对象的生命周期开始之前但在对象将占用的存储空间分配之后,或者在对象的生命周期结束之后并且在对象占用的存储空间被重用或释放之前,任何glvalue可以使用指代原始对象的那个,但只能以有限的方式使用。对于正在构造或销毁的对象,请参阅 [class.cdtor]。否则,这样的 glvalue 指的是分配的存储 ([basic.stc.dynamic.deallocation]),并且使用不依赖于其值的 glvalue 的属性是明确定义的。如果出现以下情况,则程序具有未定义的行为:

  • 左值到右值的转换 ([conv.lval]) 应用于这样的左值,

  • glvalue 用于访问非静态数据成员或调用对象的非静态成员函数,或

  • glvalue 被隐式转换 ([conv.ptr]) 为对基类类型的引用,或者

  • glvalue 用作 static_cast ([expr.static.cast]) 的操作数,除非最终转换为 cv char& 或 cv unsigned char&,或

  • glvalue 用作 dynamic_cast ([expr.dynamic.cast]) 的操作数或 typeid 的操作数。

上面提到的一点都没有发生在E 复制构造函数中,所以这个答案中的示例代码定义明确,这意味着无法访问被破坏对象的值。所以在您的示例代码中无法访问e

【讨论】:

    【解决方案2】:

    我认为它不会访问该对象,尽管需要存在一个有效的对象。

    E f(e);
    

    这会调用E 隐式定义的构造函数E::E(const E&)。显然这个构造函数的主体是空的(因为没有什么可做的)。因此,如果发生任何事情,它必须在参数传递期间发生,即在从 e 初始化 const E& 期间。

    不言而喻,这个初始化不会修改e。现在,要读取e 的值,必须进行左值到右值的转换。但是,该标准实际上表示在直接引用绑定期间不会发生这种转换1。也就是说,不执行读取。

    但是,标准确实要求必须初始化引用以引用有效的对象或函数2(尽管这受CWG 453 的约束),所以像E f(*reinterpret_cast<E*>(nullptr)); 这样的东西将是格式不正确。


    1.这是通过不规范地要求此类转换来完成的,并通过the non-normative note in [dcl.init.ref] 进一步加强。

    2。 [dcl.ref].

    【讨论】:

    • 哇,很简单。 static_cast 绰绰有余:)
    • @cpplearner 我想这个问题更多地与从e 获取的内存中读取或写入有关。 sizeof(e) 大于零(可能为 1)。我怀疑该标准在任何情况下都禁止访问填充字节,只要没有不良后果,例如由于数据竞争。考虑struct S{ double d; char c[7]; E e; };——这是很容易复制的,编译器可能会使用memcpy来复制S的数组。这意味着源es 中的填充被读取并写入目标es。禁止这样做是没有意义的。
    • @ArneVogel 这不构成对abstract machine的访问。
    • @Quentin 实际上,您根本不能使用 reinterpret_cast 进行该转换。
    猜你喜欢
    • 2019-12-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-21
    • 1970-01-01
    相关资源
    最近更新 更多