成员变量之间是否可以相互引用?这是否合法?

3

我正在处理一个类,尝试使用常量引用成员来引用其他成员变量,以便提供对这些成员的公共常量访问。是否有一种合法的方法来实现这样的效果?

代码可以编译,但是会给出关于将引用成员绑定到临时值的警告。在调用 resize() 后,A 仍然指向 null,虽然 myA 被更新了。

以下是我尝试做的事情的重现。我认为自引用部分是非法的并导致了问题,但我不确定。非常感谢您的任何建议。

class MyClass{
    private:
    int mySize;
    double * myA;

    void resize(int inSize) {
        mySize = inSize;

        delete [] myA;

        // detection for non-positive size is not included for brevity
        myA = new double [size];
    }

    public:
    const int & size;
    const double * const & A;

    MyClass(): 
    mySize(0)   , myA(nullptr)
    size(mySize), A(myA)      {}

    // dtor is not included
};

int main(){
    MyClass myObj;

    myObj.resize(42);

    // myObj.A[1] = 0.5 // seg fault
}

编辑: Michael 指出这个问题与 Aconst 限定符有关,我通过更改 A 的限定符来确认了这一点,结果如下:

double * &A -> A 正确地绑定到 myA

double * const &A -> A 正确地绑定到 myA

const double * &A -> 不兼容。编译器错误。

const double * const &A -> A 绑定到临时变量。

编辑: 看起来更好的做法是使用一个 getter 函数。


“size” 在哪里设定的?我看到了“mySize = inSize”,但是没有“size =” 的语句。即使它是合法的,这也是一个相当奇怪的结构。 - Emmet
@user4581301 我知道getter函数,只是想知道这种替代方法是否可行。我认为这种引用不会妨碍复制/赋值,因为类可以直接更改mySize/myA的值...? - Yifan Lai
@Emmet size 在初始化列表中设置。 - Yifan Lai
问题在于你必须编写自己的赋值运算符,这是保护 myA 所必需的。 - user4581301
你发布的代码应该能够编译通过:避免打错字。此外,这是一个经典的 XY 问题 的例子,因为真正的问题是为什么会有人做这种无意义的事情? - Walter
显示剩余5条评论
2个回答

4

初始化问题在于 A(myA),其中 A 的类型为 const double * const &,而 myA 的类型为 double *A 引用的类型与 myA 的类型不符合引用兼容(感谢 @guest 澄清此点)。因此,引用 A 将绑定到一个临时对象上,该对象由将 myA 隐式转换为 const double * const[dcl.init.ref]/5.4.2 的 prvalue 来实现。但是,临时对象的生命周期不会被延长[class.temporary]/5,因此,在初始化完成后,临时对象将被销毁,留下了 悬空A。这就是编译器在此处发出警告的原因。

在自己的成员中引用并没有什么不合法的。问题是:这样做有什么意义呢?虽然引用本身不是对象,也未指定它们是否占用存储空间[dcl.ref]/4,但编译器通常会通过引入字段来实现引用成员变量,以保存引用所指向对象的地址(换句话说:引用最终会被编译成基本上是指针)。因此,在您上面示例中的类型为MyClass的对象中使用了一半内存来存储指向存储地址本身的对象中的对象的地址。要知道其中一个地址的位置,就必须已经知道它们指向的东西的位置... 所以这并不违法,但可能并不是一个好主意...


谢谢你找到那些引用。省了我很多时间。 - user4581301
GCC 8 中存在同样或非常相似的错误。GCC会发出警告,但编译后程序打印了引用的错误地址。 - user4581301
@MichaelKenzel 谢谢你的回答!在gcc5.4上测试,似乎编译完美。 - Yifan Lai
@YifanLai,很不幸,我误以为我的初始答案是错误的,这导致我的答案在错误的状态下被留在这里一段时间,直到我回滚并改进它。现在,我建议您在此期间接受Walter提供的正确答案。 - Michael Kenzel
@Michael Kenzel,根据您所提到的“早期C++草案”,应该有直接绑定。但这似乎还为时过早... :-) - guest
显示剩余3条评论

1

类型double*const double*是不兼容的,所以你不能从前者的实例初始化后者的引用。然而,这并不是非法的,编译器会生成一个临时变量来绑定到引用(导致悬挂引用 - 在我看来,这不应该被允许,即应该报错)。

如果您的意图是提供对私有数据的publicconst访问,则标准方法是使用成员函数:

int size() const noexcept { return mySize; }
const double* A() const noexcept { return myA; }

这将由编译器进行优化。

使您的代码正常工作的另一种方法是添加另一个私有成员:

double*myA;
const double*myAconst;

const double* const& A = myAconst;

确保myAmyAconst始终指向相同的地址。您也可以使用myAconst代替myA,如果需要修改已分配的元素,则可以使用const_cast<double*>(myAconst)(这是const_cast<>的合法用途,因为该地址指向真正的非常量数据)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接