问题 我的初始化列表中的小错字导致无法形容的痛苦


所以,我刚刚完成了一个大型服务器应用程序的艰苦的多小时调试会话。错误最终归结为构造函数中几乎没有明显的错字。基本上,它是这样的:

template <class T>
class request_handler
{
    public:

    request_handler(T& request, Log& error_log) 
      : m_request(m_request), m_error_log(error_log)
     { 
       /*... some code ... */
     }

    ...
};

看到这个bug?好吧,我没有。问题是初始化列表中的一个小错误: m_request(m_request) 正在为自己分配一个未初始化的引用。显然,它应该阅读 m_request(request)

现在,成员变量 m_request 是类型 T&。那么 - 是不是有一些原因编译器没有警告我我在这里使用了一个未初始化的变量?

使用GCC 4.6 -Wall 国旗,如果我说:

int x;
x = x;

......它会发出警告: warning: ‘x’ is used uninitialized in this function [-Wuninitialized]

那么,为什么编译器在我分配时没有警告我 m_request 对自己:基本上为自己分配一个未初始化的引用?这本来可以节省我几个小时的烦恼。


930
2018-05-21 01:15


起源

您是否在启用完全优化的情况下编译了这个(-O3)?编译器只会在实际进行数据流分析时才会注意到其中的一些错误。还要考虑删除m_前缀。 foo(T bar) : bar(bar) 是完全明确的。 - xDD
起初我正在进行优化编译。但是在调试时,我正在编译 -g3 标志所以我可以使用调试器。即使有了 g3 标志它没有发出任何警告。 - Channel72
我不指望 -g3 无论如何要引起这个警告,它只是转储你的符号。 - xDD
我相信“小错字引起无法形容的痛苦”是C ++的官方座右铭。 ;) - Jeremy Friesner


答案:


追踪的恼人的bug。事实证明,你甚至不需要模板来静默地失败。这就行了:

class C {
        int a, b;
public:
        C(int t, int z) : a(a), b(z) { };
};

Clang警告说 -Wuninitialized

gcc伙计们的好消息:根据gnu的bugzilla, gcc 4.7.0修复了此问题

更新

在gcc 4.7.0上,添加 -Wself-init 得到这个警告(经过验证 sbellef):

tst.cc:在构造函数'C :: C(int,int)'中:tst.cc:4:9:警告:'C :: a'用自己初始化[-Wuninitialized]


11
2018-05-21 01:34



奇怪的是,我没有看到提示成员引用的bug文档,但也许修复程序处理两种情况都是一样的。 - Eitan T
好点子。 4.7.0的任何人都想给它一个旋转? - Chris Betti
GCC在我的系统上 gcc version 4.7.0 20120505 (prerelease) (GCC) 似乎没有报告错误。另一方面,clang报告正确: clang++ -Wall -c tst.cc tst.cc:4:29: warning: field is uninitialized when used here [-Wuninitialized] C(int t, int z) : a(a), b(z) { }; ^ 1 warning generated. - sbellef
@sbellef,你用过-Wself-init吗? - Jonathan Wakely
不,我曾经使用过 -Wall 只要。现在,如果我添加 -Winit-self 我明白了: tst.cc: In constructor ‘C::C(int, int)’: tst.cc:4:9: warning: ‘C::a’ is initialized with itself [-Wuninitialized] - sbellef


我喜欢使用成员使用相同名称作为构造函数参数的技巧。

template <class T>
request_handler(T& request, Log& error_log) 
 : request(request), error_log(error_log)
{ 
  /*... some code ... */
}

这将始终防止错误。你必须要小心,就像在函数体中一样 request 是指论证,而不是成员。这当然对于简单类型(如引用)无关紧要,但我不建议它用于类。


3
2018-05-21 01:42



这就是为什么它应该是 T const& request  - 你只需要参数来初始化 this->request,你不会修改它。如果您的用例更复杂,那么我建议不要这样做。但是,如果您在简单的情况下始终如一地使用此模式,则额外的好处是模式可以识别。 “哦,那个参数 Foo只是初始化 Foo。我正在调查 Bar 所以我不需要在乎 Foo“ - MSalters