假设我想创建一个对象,该对象引用某个其他类的对象 a , A 。
参考与指针
我知道 a 应该在我的新对象的生命周期内持续存在,所以我首选的方法可能是使用新对象中的引用引用 a ,传递它在建设中。
但是,也可以选择将 a 表示为新对象中的指针。这有点灵活,但是对于与参考明确的关系失去了一些东西。
在下面的代码中,类 R 使用引用引用 A ,而类 P 使用指针。
#include <iostream>
class A
{
int val; // A toy class that stores an int!
public:
A(int val_) : val(val_){}
~A(){}
void dump() const { std::cout << val << std::endl; }
};
class R
{
const A & a; // Represent the association with 'a' as a reference
public:
R(const A & a_) : a(a_){}
~R(){}
void dump() const { a.dump(); }
};
class P
{
const A * a; // Represent the association with 'a' as a pointer
public:
P(const A * a_) : a(a_){}
~P(){}
void dump() const { a->dump(); }
};
R manufactureR(int val)
{
A a(val);
R r(a);
r.dump();
return r; // Pull the rug out from under 'r' as 'a' drops out of scope.
}
P manufactureP(int val)
{
A *a = new A(val);
P p(a);
p.dump();
delete a; // Pull the rug out from under 'p' by deleting 'a'.
return p;
}
int main()
{
// Reference version: This code is dodgy, but valgrind doesn't moan
R r = manufactureR(3);
r.dump();
// Pointer version: This code is dodgy and valgrind rightly moans
P p = manufactureP(3);
p.dump();
return 0;
}
Valgrind和捕捉错误
如上所述,我的偏好是当我知道引用不需要更改时引用我的对象成员,并且引用的对象将在我的对象的生命周期中持续存在。
无论哪种方式,如果在新对象存在期间 a 由于编码错误而丢失/破坏,那么显然很糟糕,但新对象继续提及它。
但可以说更糟糕的是无法发现错误何时发生。
最后的代码创建 R 和 P 中的每一个的实例,然后通过销毁引用的/指向对象来故意破坏它们。
我得到的输出是这些:
$./test
3
1726372352
3
0
有趣的是,当发生不幸事件并且引用的对象消失时,valgrind并没有像我期望的那样报告错误。在释放 a 后,它会在指针版本中发现无效读取大小4 。
如果通过使用引用,存在错误导致我无意中读取死记忆的风险,并且valgrind无法检测到它,那么它足以让我使用指针。
问题
有没有其他人观察/遭受过valgrind的这种不一致,或者因为我是C ++的新手(来自C的长期职业生涯),在尝试使用引用作为对象成员时,我误解了一些基本的东西?也许这个例子是有缺陷的,因为我在 manufactureR 的回归中依赖于RVO,有效地复制(没有赋值) r 中的引用。
详情
CentOS 6.5; gcc version 4.7.2 20121015 (Red Hat 4.7.2-5) (GCC); valgrind-3.8.1