为什么“构造函数调用”不会触发任何编译器(g ++)警告?

时间:2016-02-16 11:42:39

标签: c++ multithreading c++11 compiler-warnings

我实现了一个应该是线程安全的共享库。为了保护我在C ++ 11标准中使用std::lock_guard<std::mutex>的关键区域。

错字是我省略了对象本身:

std::lock_guard<std::mutex>(getMutexObj());

而不是

std::lock_guard<std::mutex> lock_obj(getMutexObj());

并在每个地方复制/粘贴...不用说,当多线程应用程序开始无法预测崩溃时,我花了一段时间才导致它。

为了涵盖所有要点,getMutexObj()的声明和互斥锁本身如下:

...
mutable std::mutex  m_mutex;
...
std::mutex& getMutexObj() const
{
    return m_mutex;
}

所有代码都使用g++ 5.2.0编译,并带有以下警告标志:

WARNINGS := -pedantic \
            -Wall \
            -Wextra \
            -Werror \
            -Wconversion \
            -Woverloaded-virtual \
            -Wcast-qual \
            -Wctor-dtor-privacy \
            -Wdisabled-optimization \
            -Wuninitialized \
            -Wformat=2 \
            -Winit-self \
            -Wlogical-op \
            -Wmissing-declarations \
            -Wmissing-include-dirs \
            -Wold-style-cast \
            -Wredundant-decls \
            -Wshadow \
            -Wsign-conversion \
            -Wsign-promo \
            -Wstrict-null-sentinel \
            -Wstrict-overflow=5 \
            -Wswitch-default \
            -Wundef \
            -Wunused \
            -Wfloat-equal \
            -Wsuggest-final-methods \
            -Wsuggest-final-types \
            -Wzero-as-null-pointer-constant

编译器怎么没有在std::lock_guard<std::mutex>(getMutexObj());上发出任何警告?

我尝试了以下代码,看看编译器是否会抛出警告:

    std::lock_guard<std::mutex>(getMutexObj());
    int(23);
    23;
    uint16_t remove_me = 23;

对于第2,3和4行,我收到警告但不是第1行......为什么?

<.../path/...>:32:16: error: statement has no effect [-Werror=unused-value]
         int(23);
                ^
<.../path/...>:33:11: error: statement has no effect [-Werror=unused-value]
         23;
           ^
<.../path/...>:34:18: error: unused variable 'remove_me' [-Werror=unused-variable]
         uint16_t remove_me = 23;
                  ^
cc1plus: all warnings being treated as errors

EDIT1

我注意到这个问题有点令人困惑,因为几乎所有的答案都与

有关
  

对于第2,3和4行我收到警告但不是第1行......为什么?

然而,真正的问题是:

编译器怎么没有在std::lock_guard<std::mutex>(getMutexObj());上发出任何警告?

是否有任何开关使编译器警告这样的代码???

EDIT2

正如其评论中提到的 cpplearner 一样,std::lock_guard<std::mutex>(getMutexObj());被视为名为getMutexObj的函数的函数声明,返回std::lock_guard<std::mutex>

我查看了反汇编,发现在std::lock_guard<std::mutex>(getMutexObj());的情况下,根本没有相关的操作码。

然而,当我把它改为

std::lock_guard<std::mutex>{getMutexObj()};

并查看了反汇编,它被编译为临时对象创建,但没有任何警告。

5 个答案:

答案 0 :(得分:7)

23;不同,消息&#34;错误:语句无效&#34; ,创建未命名的临时lock_guard 具有锁定和解锁互斥锁的效果。

这具有其他线程可见的内存屏障的副作用。

答案 1 :(得分:5)

std::lock_guard<std::mutex>(getMutexObj());

这实际上应该给出一个错误,因为它声明了一个名为getMutexObj的函数,但是已经使用不同的返回类型声明了(并且你不能在返回类型上重载)。这是一个GCC错误,我已将其报告为bug 69855

这种情况不同:

std::lock_guard<std::mutex>{getMutexObj()};

这个 会创建一个临时的,但是这里没有警告,因为编译器并不神奇。

警告被添加到编译器以捕获常见错误,但有人必须实际执行代码来检查它,并发出警告。这并不是通过魔法或仙女来实现的,而是在晚上没有人看的时候改进编译器。

在这种情况下,编译器会看到您正在创建一个临时变量,它具有副作用(它写入全局内存位置,互斥锁并发出内存障碍)。编译器并不知道那些副作用并不完全是你想做的,因为它并不神奇。

如果编译器在这里发出警告会很好,并且有人确实为GCC编写了一个补丁来警告这一点,请参阅bug 36587,但正如我在该错误报告中指出的那样,它也会警告有效码。如果编译器在每次创建具有副作用的临时变量时发出警告,那将是很糟糕的。这是使用类似语法的有效方案:

std::ofstream( "./lockfile" );

这将打开一个具有特定名称的ofstream,如果该文件尚未存在,则会创建该文件。编译器不应该警告这一点,因为它完全有效。

因此,为了警告您的示例,而不是其他类似的代码片段,需要一些额外的信息来告诉编译器永远不应该以这种方式使用lock_guard类型。这意味着以某种方式注释lock_guard并教导编译器识别该注释。同样,仙女不会来做这项工作,所以它没有发生,因为它没有被优先考虑,也没有人写过补丁。

GCC支持构造函数的warn_unused属性,请参阅bug 55203,但这对lock_guard没有帮助,因为正确使用该类意味着它始终是&#34未使用&#34; (施工后你永远不会参考它)。对于这种情况,需要实现不同的属性和警告。

答案 2 :(得分:4)

简单的答案是 - 编译器没有触发任何警告,因为这是一个有效的表达式,可以(理论上)故意使用。表达式:

std::lock_guard<std::mutex>(getMutexObj());

是类型转换的功能表示法,在标准中定义:

  

5.2.3显式类型转换(功能表示法)

     

1)简单类型说明符(7.1.6.2)或类型名称说明符(14.6)   后面跟一个带括号的表达式列表构造一个值   给定表达式列表的指定类型。如果表达式列表是a   单表达式,类型转换表达式是等价的(in   定义,如果在意义上定义)到相应的演员   表达式(5.4)。 (...)

并被解释为静态强制转换(5.2.9.4):

  

表达式e可以使用a显式转换为类型T.   static_cast形式为static_cast(e)如果声明为T t(e);   对于一些发明的临时变量t(8.5),它是格式良好的。该   这种显式转换的效果与执行相同   声明和初始化然后使用临时变量   作为转换的结果。 (...)

编译器无法提供它为

所做的任何警告
int(23);
uint16_t remove_me = 23;

因为您没有创建变量(可能未使用)而表达式 具有效果 - 它正在调用std::lock_guard<std::mutex>的构造函数。

答案 3 :(得分:3)

guard是一种聚合数据类型(与你比较它的基本类型相反),其中构造函数可以包含自定义代码;所以“声明没有效果”是(或可能)不正确 - 你可能只想构造并立即销毁一个物体。 “unused variable”也不匹配,因为临时对象没有名称,并且在块结束之前不会持久。

答案 4 :(得分:2)

我相信编译器不知道引用变量的确切上下文。引发警告的情况不是引用值,它们是原始类型。编译器不知道你要完成什么,因为它不知道库的作用。

此外,在这种情况下,您实际上是锁定变量。但它可能是别的东西。就像在程序之外开始一些事情如果你不想阻止你开始的事情,你真的不需要一个名字。