C++引用是如何工作的?

3

在使用C++工作了15年后,我发现自己并没有完全理解引用。

class TestClass
{
public:
    TestClass() : m_nData(0)
    {
    }

    TestClass(int n) : m_nData(n)
    {
    }

    ~TestClass()
    {
        cout << "destructor" << endl;
    }

    void Dump()
    {
        cout << "data = " << m_nData << "  ptr = 0x" << hex << this << dec << endl;
    }

private:
    int m_nData;
};

int main()
{
    cout << "main started" << endl;

    TestClass& c = TestClass();
    c.Dump();

    c = TestClass(10);
    c.Dump();

    cout << "main ended" << endl;

    return 0;
}

// prints:
// main started
// data = 0  ptr = 0x0012FF54
// destructor
// data = 10  ptr = 0x0012FF54
// main ended
// destructor

我从这个测试中了解到TestClass实例是在堆栈上创建并由第一个TestClass构造函数初始化(这正确吗?). 这个实例是什么时候分配的:当main函数被加载还是当引用赋值被执行?它是什么时候销毁的?
在第二个引用赋值之后,对象地址没有改变。这是否意味着析构函数和构造函数适用于同一内存区域?还是内存被释放(动态地?在堆栈上?)然后重新分配?
我知道关于堆栈和堆分配对象的生命周期,它们的构造函数和析构函数,但我无法理解这个程序中具体发生了什么。
编辑:感谢大家。我试图在这个测试中复现其他(更复杂)程序的行为。你们的评论帮助我理解了我的错误和另一个我正在与之斗争的程序...
修正后的代码如下:
int main()
{
    cout << "main started" << endl;
    TestClass t;

    TestClass& c(t);
    c.Dump();

    c = TestClass(10);
    c.Dump();

    cout << "main ended" << endl;
    return 0;
}

注意:2022年6月29日更新:由于丹尼尔·沃克最新的编辑,这个问题看起来像是一堆废话。本人不对此负责。


2
这段代码无法编译。你不能将非 const 引用绑定到临时对象上。 - Oliver Charlesworth
1
有没有不将自动对象放在堆栈上的 C++ 实现?如果有,它们是哪些? - Jeremy Friesner
你用哪个编译器编译的?它不应该能编译。 - Cheers and hth. - Alf
@Alf P. Steinbach,这不是在Cray上的堆栈,而是一个“堆栈”帧的链表。一些alloca()实现的源代码带有与此兼容性的痕迹。 - Cubbi
2
请保持评论建设性和主题相关。 - Tim Post
显示剩余14条评论
5个回答

5

您的代码存在多个问题,最终将毫无意义。不过,让我们来一步步解决它。

1) 您只能将临时对象绑定到 const 引用上,从而延长其生命周期:

const TestClass & c = TestClass();

2) 现在我们不能使用 dump,因为你没有将其声明为 const

void Dump() const

3) 说c = TestClass()是一种赋值。然而,c现在是一个常量引用,不能被赋值,因为赋值是非常量的(显然)。我们来绕过这个问题:

const_cast<TestClass&>(c) = TestClass(10);

现在我们已经为临时但扩展的对象c分配了一个新值,一切都是应该的:

main started
data = 0  ptr = 0x0xbfa8219c
destructor
data = 10  ptr = 0x0xbfa8219c
main ended
destructor

指针是相同的,因为只有一个对象,即c引用的(临时)对象。对其进行赋值是一种未定义行为,仅用于此演示目的而已。中间析构函数是第二个临时TestClass(10)的析构函数。

2
TestClass& c = TestClass(); // TestClass() temporary doesn't persist beyond this expression.
c.Dump();
< p > TestClass() 创建了一个临时对象,您无法引用它。

const TestClass& c = TestClass();

const 限定符将临时对象的生命周期延长到 c 对象的作用域结束。


2
TestClass& c = TestClass();

这段代码甚至不能编译通过!

试图将临时对象绑定到非const引用会导致编译错误。

然而,你可以将临时对象绑定到const引用:

{
   const TestClass& c = TestClass();
   //use c 
   //....
}//<-------- the temporary will be destroyed here.

在这种情况下,临时对象的生命周期会延长到引用的生命周期,也就是说,当引用变量超出作用域时,临时对象将被销毁,如上所示。

这个在VC++ 2010中编译,带有C4239警告(4级)。 - Alex F
@Alex:这个警告清楚地提到:使用了非标准扩展:'token':从'type'转换为'type' ..... 你看到粗体字的内容了吗? - Nawaz
1
有些警告应该被视为错误... MCVC非常宽容。 - AJG85

2

1) 你无法获得对临时对象的非const引用。

2) 在代码行c = TestClass(10);中,调用了operator=(...)。


1
一个好的方法是比较指针引用...(在汇编中,引用通常以相同的方式实现,通常使用ebx寄存器)。主要区别在于引用在初始化后是常量...
但是,行const TestClass& c = TestClass();const TestClass* const pc = &TestClass();平行,因此对象将在堆栈上创建和销毁,pc仍将保持相同的地址。

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