检测或避免在编译时
临时死引用的与-O3
与-O2
编译也许当以下最低十岁上下的程序段错误,但-O0
执行罚款(与铿锵4.0):检测或避免在编译时
#include <iostream>
class A {
public:
virtual void me() const { std::cerr << "hi!\n"; }
};
class B {
public:
B(const A& a_) : a(a_) {}
virtual void me() const { a.me(); }
private:
const A& a;
};
class C {
public:
C(const B& b_) : b(b_) {}
void me() const { b.me(); }
public:
const B& b;
};
int main() {
C c = C(A());
c.me();
}
的原因是该c.b
被初始化到从一个临时A
构造B
类的临时对象的引用。构造c.C()
退出后,临时B
走了,但对它的引用仍然c.b
。
我可以采用什么好的做法,以避免出现这种情况,因为我无法控制的B
或A
执行?是否有静态分析仪能够检测到这种情况? (我的scan-build
版本没有发现问题。)
我个人不喜欢有成员变量的引用。它们不能被复制或移动,并且该类的用户必须确保该对象超出参考本身。一个异常可能是一个内部辅助类,它被设计成只用作临时对象,比如函子。
更换一个指针引用应该是简单的,然后如果你也是在构造函数中使用指针:
class C {
public:
C(const A *a_) : a(a_) {}
private:
const A *a;
};
...等等。如果类是非常大的,你觉得懒惰,不想更改成员,你可以改变的构造:
class C {
public:
C(const A *a_) : a(*a_) {}
private:
const A &a;
};
为了滥用这个类,作为OP说,你必须写类似:
C c = C(&A());
然后错误应该是显而易见的:采用指针来临时是一个非常糟糕的主意! PS:我会为你的构造函数添加一个explicit
,只是为了避免它参与自动转换,我认为这是自己的问题的一部分。
我就从B
和C
获得单独的类(甚至可能使用模板类)。
这些类将含有变成a
和b
指东西的非基准部件。
我然后实现这些派生类所需的拷贝构造函数/赋值运算符,以防止悬空引用。
(然后,我会与B
和C
的作者进行强有力的对话)。
这是一个现有的代码库,有什么方法可以找到这种反模式的所有用途? – krlmlr
好,U现在,右值引用。它们只绑定到临时对象。我想也许这可以作为一个只有左值的ref-wrapper抽象出来。 –
通常,不要声明类型引用的成员变量。改用指针。如果你在构造函数中得到一个指针,结果代码将会很明显。 – rodrigo
@rodrigo:这听起来很有趣。我想这也可以工作,如果我只声明构造函数参数作为指针,然后初始化引用?我宁愿不重写所有使用引用的代码... – krlmlr