我最近发现我的C ++程序中的大多数错误都是 表格如下例所示:
#include <iostream>
class Z
{
public:
Z(int n) : n(n) {}
int n;
};
class Y
{
public:
Y(const Z& z) : z(z) {}
const Z& z;
};
class X
{
public:
X(const Y& y) : y(y) {}
Y y;
};
class Big
{
public:
Big()
{
for (int i = 0; i < 1000; ++i) { a[i] = i + 1000; }
}
int a[1000];
};
X get_x() { return X(Y(Z(123))); }
int main()
{
X x = get_x();
Big b;
std::cout << x.y.z.n << std::endl;
}
输出:1000
我希望这个程序输出123(x.y.z.n的值设置为 get_x())但是“Big b”的创建会覆盖临时Z. 结果,现在引用对象Y中的临时Z. 用Big b覆盖,因此输出不是我想要的 期望的。
当我用gcc 4.5和“-Wall”选项编译这个程序时,它 没有发出警告。
修复显然是要删除成员Z中的引用 Y类。但是,Y类通常是我没有的库的一部分 开发(最近的boost :: fusion),以及另外的情况 比我给出的这个例子复杂得多。
这对gcc或任何其他软件有一些选择 允许我最好在编译时检测这些问题,但是 甚至运行时总比没有好?
谢谢,
克林顿
答案 0 :(得分:2)
我几个月前在clang-dev邮件列表上提交了这样的案例,但当时没有人有时间处理它(不幸的是,我也没有)。
Argyrios Kyrtzidis目前正在研究它,这是他对此事的最新更新(格林尼治标准时间11月30日23:04):
我恢复了之前的提交 更好地解决 http://lists.cs.uiuc.edu/pipermail/cfe-commits/Week-of-Mon-20101129/036875.html。 例如对
struct S { int x; };
int &get_ref() { S s; S &s2 = s; int &x2 = s2.x; return x2; }
我们得到了
t3.cpp:9:10: warning: reference to stack memory associated with local variable 's' returned
return x2;
^~
t3.cpp:8:8: note: binding reference variable 'x2' here
int &x2 = s2.x;
^ ~~
t3.cpp:7:6: note: binding reference variable 's2' here
S &s2 = s;
^ ~
1 warning generated.
之前的尝试未能通过自托管测试,所以我希望这次尝试能够通过。我很高兴Argyrios无论如何都在研究它:)
不可否认,这还不完美,因为这是一个非常复杂的问题(在某种程度上让我想起指针别名),但这仍然是朝着正确方向迈出的一大步。
你可以针对这个版本的Clang测试你的代码吗?我很确定Argyrios会欣赏反馈(无论是否被检测到)。
答案 1 :(得分:0)
[编辑第三个子弹以演示可能有帮助的技术] 当语言允许通过值或引用以相同的调用者语法传递参数时,这就是您所遇到的兔子洞。您有以下选择:
将参数更改为非const引用。临时值与非const引用类型不匹配。
在有可能的情况下,完全删除引用。如果你的const引用没有表明调用者和被调用者之间的逻辑共享状态(如果他们确实不会经常发生这个问题),可能会插入它们以试图避免复杂类型的天真复制。现代编译器具有先进的复制椭圆优化功能,在大多数情况下,传递值与传递引用一样高效;请参阅http://cpp-next.com/archive/2009/08/want-speed-pass-by-value以获得一个很好的解释。如果您将值传递给可能修改临时值的外部库函数,则不会执行复制椭圆,但如果是这种情况,那么您要么不将它们作为const引用传递或故意丢弃const - 原始版本中的。这是我首选的解决方案,因为它让编译器担心复制优化,并让我担心代码中的其他错误源。
如果您的编译器支持右值引用,请使用它们。如果您至少可以编辑担心此问题的函数的参数类型,则可以像这样定义包装器元类:
模板&lt; typename T&gt; class need_ref {
T&amp; T&amp; REF _;公共:
need_ref(T&amp;&amp; x){/ * nothing * /}
need_ref(T&amp; x):ref_(x){/ * nothing * /}
运营商T&amp; (){return ref_; }
};
然后替换T&amp;类型的参数带有need_ref类型的参数。例如,如果您定义以下
class user {
int&amp; z;
公共:
user(need_ref&lt; int&gt; arg):z(arg){/ * nothing * /}
};
然后你可以安全地初始化一个user类型的对象,代码形式为“int a = 1,b = 2; user ua(a);”,但如果你试图初始化为“user sum(a + b) )“或”用户五(5)“您的编译器应在need_ref()构造函数的第一个版本内生成未初始化的引用错误。该技术显然不仅限于构造函数,也不会产生运行时开销。
答案 2 :(得分:-1)
这里的问题是代码
Y(const Z& z) : z(z) {}
因为成员'z'通过对形式参数'z'的引用进行初始化。一旦构造函数返回引用,就引用一个不再有效的对象。
我不认为编译器会在很多情况下检测到这样的逻辑缺陷。然后修复IMO显然要注意这些类并以适合其设计的方式使用它们。这应该由图书馆供应商记录。
顺便说一下,如果可能的话,最好将成员'Y :: z'命名为'Y :: mz'。表达式'z(z)'不是很吸引人