我实现了一个应该是线程安全的共享库。为了保护我在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()};
并查看了反汇编,它被编译为临时对象创建,但没有任何警告。
答案 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)
我相信编译器不知道引用变量的确切上下文。引发警告的情况不是引用值,它们是原始类型。编译器不知道你要完成什么,因为它不知道库的作用。
此外,在这种情况下,您实际上是锁定变量。但它可能是别的东西。就像在程序之外开始一些事情如果你不想阻止你开始的事情,你真的不需要一个名字。