如何在编译或运行时检测const引用临时问题?

时间:2010-12-01 02:10:34

标签: c++ class reference temporary

我最近发现我的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或任何其他软件有一些选择 允许我最好在编译时检测这些问题,但是 甚至运行时总比没有好?

谢谢,

克林顿

3 个答案:

答案 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)'不是很吸引人