如何在编译或运行时检测对临时问题的常量引用? 我最近发现我的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; } #包括 Z类 { 公众: Z(int n):n(n){} int n; }; Y类 { 公众: Y(常数Z&Z):Z(Z){} 常数Z&Z; }; X类 { 公众: X(常数Y&Y):Y(Y){} Y; }; 班级大 { 公众: 大() { 对于(inti=0;i
[编辑第三个项目符号以演示一种可能有用的技术] 当一种语言允许使用相同的调用方语法通过值或引用传递参数时,这就是一个兔子洞。您有以下选项:如何在编译或运行时检测对临时问题的常量引用? 我最近发现我的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; } #包括 Z类 { 公众: Z(int n):n(n){} int n; }; Y类 { 公众: Y(常数Z&Z):Z(Z){} 常数Z&Z; }; X类 { 公众: X(常数Y&Y):Y(Y){} Y; }; 班级大 { 公众: 大() { 对于(inti=0;i,c++,class,reference,temporary,C++,Class,Reference,Temporary,[编辑第三个项目符号以演示一种可能有用的技术] 当一种语言允许使用相同的调用方语法通过值或引用传递参数时,这就是一个兔子洞。您有以下选项: 将参数更改为非常量引用。临时值与非常量引用类型不匹配 在可能的情况下,完全删除引用。如果您的常量引用未指示调用方和被调用方之间的逻辑共享状态(如果它们这样做,则不会经常发生此问题),插入它们可能是为了避免对复杂类型的简单复制。现代编译器具有高级复制省略优化,在大多数情况下,这些优化使传递值与传递引用一样高效;请参阅,以获得详细解释。如果将值传递给外部库函数
- 将参数更改为非常量引用。临时值与非常量引用类型不匹配
- 在可能的情况下,完全删除引用。如果您的常量引用未指示调用方和被调用方之间的逻辑共享状态(如果它们这样做,则不会经常发生此问题),插入它们可能是为了避免对复杂类型的简单复制。现代编译器具有高级复制省略优化,在大多数情况下,这些优化使传递值与传递引用一样高效;请参阅,以获得详细解释。如果将值传递给外部库函数,则显然不会执行复制省略这可能会修改临时变量,但如果是这种情况,那么您要么不将它们作为常量引用传递,要么故意在原始版本中丢弃常量。这是我首选的解决方案,因为它让编译器担心复制优化,让我不用担心代码中的其他错误源
- 如果您的编译器支持右值引用,请使用它们。如果您至少可以编辑担心此问题的函数的参数类型,则可以定义如下所示的包装器元类:
然后,您可以使用“int a=1,b=2;user ua(a);”形式的代码安全地初始化user类型的对象,但是如果您尝试初始化为“user sum(a+b)”或“user five(5)”,则编译器应该在need_ref()的第一个版本内生成未初始化的引用错误构造函数。该技术显然不仅限于构造函数,而且不会造成运行时开销。这里的问题是代码
Y(const Z& z) : z(z) {}
由于成员“z”是用对形式参数“z”的引用初始化的。一旦构造函数返回,引用将引用不再有效的对象
我不认为编译器在很多情况下会或能够检测到这样的逻辑缺陷。显然,IMO的解决方案是了解这些类,并以适合其设计的方式使用它们。这应该由库供应商记录下来
顺便说一句,如果可能的话,最好将成员“Y::z”命名为“Y::mz”。表达“z(z)”不是很吸引人几个月前我在clang dev邮件列表上提交了这样的案例,但当时没有人有时间处理它(不幸的是,我也没有) Argyrios Kyrtzidis目前正在对此进行研究,以下是他对此事的最后更新(格林尼治标准时间11月30日23时04分): 我恢复了以前的承诺,非常 最好把它修好 . e、 g.为了 我们得到 上一次尝试未能通过自托管测试,因此我希望这次尝试能够通过。我很高兴Argyrios正在调查此事:) 诚然,它还不是完美的,因为它是一个相当复杂的问题(在某种程度上让我想起了指针别名),但这仍然是朝着正确方向迈出的一大步
你能用这个版本的Clang测试一下你的代码吗?我很确定Argyrios会很感激你的反馈(不管是否被检测到)。我有点惊讶你得到的是1000而不是1123。Gabe:为什么是1123?Big的构造函数将第零个元素设为1000。它不会将1000添加到第零个元素。如果它是{a[I]+=I+1000;}我知道你是从哪里来的。相关:Chubsdad:你刚刚重复了我在这里的问题中提出的问题。我已经提到:“修复方法显然是删除Y类中成员Z的引用。但是,通常Y类是我没有开发的库的一部分(boost::fusion最近),此外,情况比我给出的示例复杂得多。“我无法更改库代码。我的问题不是如何解决问题(我知道如何解决)。问题是如何检测问题。像这样的问题花了我几个小时来检测,一分钟来修复。这是我需要帮助的检测,而不是修复。我编辑了我的答案,以显示一种技术,如果您可以调整调用堆栈的至少一个级别的参数类型,它将生成编译时错误(例如,上面示例中的X、Y和/或/Z构造函数。)如果您不能编辑任何代码,那么我猜您只能使用实验版本的clang,因为我不知道在任何主流编译器中有任何命令行开关可以启用此类测试。
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.