【问题标题】:Does a member have to be initialized to take its address?是否必须初始化成员才能获取其地址?
【发布时间】:2019-09-28 10:37:02
【问题描述】:

我可以在初始化成员之前初始化指向数据成员的指针吗?换句话说,这是有效的 C++ 吗?

#include <string>

class Klass {
public:
    Klass()
        : ptr_str{&str}
        , str{}
    {}
private:
    std::string *ptr_str;
    std::string str;
};

this 的问题和我的类似,但是那里的顺序是正确的,答案是

我建议不要这样编码,以防有人改变了你班级成员的顺序。

这似乎意味着颠倒订单是非法的,但我不能确定。

【问题讨论】:

  • 只是好奇你为什么需要它?
  • @vahancho 我在类的末尾添加了一个私有向量(因此它与接口在物理上是分开的)但是这个向量需要指向一些公共成员的指针。我想知道是否必须将其向上移动,或者这样是否可以。
  • 只要确保在会员实际存在之前不要尝试访问它
  • @Ayxan Language-lawyer 的问题可能会给本网站带来有趣的讨论。因此,我建议您再等一会儿,如此迅速地接受第一个答案。虽然它可能是正确的,但其他意见也可能很有价值。
  • @DanielLangr 不错的建议。

标签: c++ pointers initialization language-lawyer


【解决方案1】:

是否必须初始化成员才能获取其地址?

没有。

我可以在初始化成员之前初始化指向数据成员的指针吗?换句话说,这是有效的 C++ 吗?

是的。是的。

一元 & 的操作数不需要初始化是没有限制的。一元&运算符的规范中有一个例子:

int a;
int* p1 = &a;

这里a的值是不确定的,指向它就可以了。

该示例未演示的是在其生命周期开始之前指向一个对象,这就是您的示例中发生的情况。如果存储空间被占用,使用指向对象在其生命周期之前和之后的指针是明确允许的。标准草案说:

[basic.life] 在对象的生命周期开始之前但在对象将占用的存储空间已分配之后,或者在对象的生命周期结束之后并且在对象占用的存储空间被重用或释放之前, 任何表示对象将要或曾经所在的存储位置地址的指针都可以使用,但只能以有限的方式使用...

该规则继续列出如何限制使用。凭常识就能过得去。简而言之,您可以像对待void* 一样对待它,除了违反这些限制是UB 而不是格式错误。引用也有类似的规则。

对计算非静态成员的地址也有限制。标准草案说:

[class.cdtor] ... 要形成指向对象obj 的直接非静态成员(或访问其值)的指针,obj 的构造应该已经开始,并且它的销毁不应已完成,否则指针值的计算(或访问成员值)会导致未定义的行为。

Klass的构造函数中,Klass的构造已经开始,销毁还没有完成,所以满足上述规则。


附:您的类是可复制的,但副本将具有指向另一个实例成员的指针。考虑这对你的班级是否有意义。如果没有,您将需要实现自定义复制和移动构造函数和赋值运算符。像这样的自引用很少见,您可能需要自定义定义,但不需要自定义析构函数,因此它是五(或三)规则的例外。

P.P.S 如果您的意图是指向成员之一,并且除了成员之外没有其他对象,那么您可能希望使用指向成员的指针而不是指向对象的指针。

【讨论】:

  • 引用标准的相关部分会很好。这些可能是相关的:eel.is/c++draft/basic.life#6.sentence-1eel.is/c++draft/class.cdtor#3.sentence-2
  • @DanielLangr 感谢您查找这些内容。
  • 您的示例和 OP 的示例之间存在细微差别。在您的示例中,变量 a 的生命周期已经开始,但尚未初始化。在 OP 的示例中,成员的地址是在其生命周期之前获取的。鉴于您对标准的引用已经充分解决了 OP 的问题,我认为应该删除该示例。
  • @Brian OP 专门询问初始化。引用的规则没有明确解决初始化问题(没有规则解决它;据我所知,根本没有与初始化相关的规则)。当然,缺乏生命周期意味着缺乏初始化,但我更喜欢明确。在我看来,展示缺乏初始化的例子是有用的,即使它在技术上被生命周期规则变得多余。
  • this 评论说int a; 初始化。默认初始化(具有不确定的值)。
【解决方案2】:

有趣的问题。

它是合法的,并且会“工作”,尽管几乎没有。有一点与类型有关的“但是”使整个事情变得有点尴尬,味道不好(但不是非法的),并且可能在某些涉及继承的边界情况下使其非法。

当然,您可以获取任何对象的地址,无论它是否已初始化,只要它存在于范围内并且有一个您可以在前面加上 operator&amp; 的名称。取消引用指针是另一回事,但这不是问题所在。

现在,微妙的问题是the standard 将非静态结构成员的operator&amp; 的结果定义为 "“指向类型 T 的类 C 的成员的指针”,并且是指定 C::米”.

这基本上意味着ptr_str{&amp;str}str的地址,但是类型不是pointer-to,而是pointer-to-成员。然后它被隐式地和静默地转换为 pointer-to

换句话说,尽管您不需要显式编写&amp;this-&gt;str,但这就是它的类型——它就是它的本质和含义[1]

this 是否有效,在初始化列表中使用它是否安全?嗯,是的,只是......几乎。使用它是安全的as long as it's not being used to access uninitialized members or virtual functions, directly or indirectly。碰巧的是,这里就是这种情况(在不同的、可以说是人为的情况下可能不是这种情况)。


[1] 有趣的是,第 4 段以一个子句开头,该子句说当您将内容放在括号中时不会形成任何成员指针。这很了不起,因为大多数人可能会这样做只是为了 100% 确保他们得到正确的运算符优先级。但如果我没看错,那么&amp;this-&gt;foo&amp;(this-&gt;foo) 一点也不一样!

【讨论】:

  • 不,&amp; 具有非限定名称 从不 创建指向成员的指针。如果你想要一个指向成员的指针,你必须写&amp;Klass::str。同样,&amp;this-&gt;foo&amp;(this-&gt;foo) 是相同的,它们都创建普通指针,因为它们是成员访问表达式,而不是限定名称。
  • 您链接到的标准规则以“如果操作数是 qualified-id”开头,这就是它不适用于此处的原因。
  • 此外,没有从指向成员的指针类型到指针类型的隐式转换(它将绑定到什么对象?)。
猜你喜欢
  • 1970-01-01
  • 2023-03-18
  • 1970-01-01
  • 2019-09-25
  • 1970-01-01
  • 1970-01-01
  • 2010-09-26
  • 1970-01-01
  • 2016-02-03
相关资源
最近更新 更多