为什么 "constructor invocation" 不触发任何编译器 (g++) 警告?
Why "constructor invocation" doesn't fire any compiler (g++) warning?
我实现了一个线程安全的共享库。为了保护关键区域,我使用了 C++11 标准中的 std::lock_guard<std::mutex>
。
打字错误是我省略了对象本身:
std::lock_guard<std::mutex>(getMutexObj());
而不是
std::lock_guard<std::mutex> lock_obj(getMutexObj());
而且 copy/pasted 它在每个地方...不用说,当多线程应用程序开始不可预测地崩溃时,我花了一段时间才找到它的根源。
为了涵盖所有要点,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
我注意到这个问题有点令人困惑,因为几乎所有答案都与
有关
for line 2,3 and 4 I got warnings BUT not for line 1... WHY?
然而这里真正的问题是:
为什么编译器没有在 std::lock_guard<std::mutex>(getMutexObj());
上发出任何警告?
是否有任何开关可以让编译器对此类代码发出警告???
EDIT2
正如 cpplearner 在他的评论中提到的那样,std::lock_guard<std::mutex>(getMutexObj());
被视为名为 getMutexObj
的函数声明,其中 returns std::lock_guard<std::mutex>
.
我看了反汇编,发现在 std::lock_guard<std::mutex>(getMutexObj());
的情况下,根本没有相关的操作码。
然而当我把它改成
std::lock_guard<std::mutex>{getMutexObj()};
并查看反汇编,它被编译为临时对象创建但再次没有任何警告。
不同于 23;
消息 "error: statement has no effect",创建一个未命名的临时文件 lock_guard
具有锁定和解锁互斥量的作用。
这具有对其他线程可见的内存屏障的副作用。
守卫是一种聚合数据类型(与您与之比较的原始类型相反),其中构造函数可以包含自定义代码;所以 "statement has no effect" 是(或可能是)不正确的——你的意图很可能只是构造并立即销毁一个对象。 "unused variable" 也不匹配,因为临时对象没有名称并且在块结束之前不会持久存在。
我认为是因为编译器不知道引用变量的确切上下文。引发警告的情况不是引用值,它们是原始类型。编译器不知道您要完成什么,因为它不知道库的作用。
此外,在这种情况下,您实际上是在锁定变量。但它可能是别的东西。就像在你的程序之外开始一些事情。如果你不打算停止你开始的事情,你真的不需要一个名字。
简单的答案是 - 编译器没有发出任何警告,因为这是一个可以(理论上)有意使用的有效表达式。表达式:
std::lock_guard<std::mutex>(getMutexObj());
是类型转换的函数符号,定义在标准中:
5.2.3 Explicit type conversion (functional notation)
1) A simple-type-specifier (7.1.6.2) or typename-specifier (14.6)
followed by a parenthesized expression-list constructs a value of the
specified type given the expression list. If the expression list is a
single expression, the type conversion expression is equivalent (in
definedness, and if defined in meaning) to the corresponding cast
expression (5.4). (...)
并被解释为静态转换 (5.2.9.4):
An expression e can be explicitly converted to a type T using a
static_cast of the form static_cast(e) if the declaration T t(e);
is well-formed, for some invented temporary variable t
(8.5). The
effect of such an explicit conversion is the same as performing the
declaration and initialization and then using the temporary variable
as the result of the conversion. (...)
编译器无法发出它为
发出的任何警告
int(23);
uint16_t remove_me = 23;
因为您没有创建变量(可能未使用)并且表达式 确实 有效果 - 它正在调用 std::lock_guard<std::mutex>
的构造函数。
std::lock_guard<std::mutex>(getMutexObj());
这实际上应该给出一个错误,因为它声明了一个名为 getMutexObj
的函数,但它已经用不同的 return 类型声明了(并且你不能在 return 上重载类型)。这是一个 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
没有帮助,因为 class 的正确使用意味着它是总是 "unused" (你永远不会在构造后引用它)。对于这种情况,需要实施不同的属性和警告。
我实现了一个线程安全的共享库。为了保护关键区域,我使用了 C++11 标准中的 std::lock_guard<std::mutex>
。
打字错误是我省略了对象本身:
std::lock_guard<std::mutex>(getMutexObj());
而不是
std::lock_guard<std::mutex> lock_obj(getMutexObj());
而且 copy/pasted 它在每个地方...不用说,当多线程应用程序开始不可预测地崩溃时,我花了一段时间才找到它的根源。
为了涵盖所有要点,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
我注意到这个问题有点令人困惑,因为几乎所有答案都与
有关for line 2,3 and 4 I got warnings BUT not for line 1... WHY?
然而这里真正的问题是:
为什么编译器没有在 std::lock_guard<std::mutex>(getMutexObj());
上发出任何警告?
是否有任何开关可以让编译器对此类代码发出警告???
EDIT2
正如 cpplearner 在他的评论中提到的那样,std::lock_guard<std::mutex>(getMutexObj());
被视为名为 getMutexObj
的函数声明,其中 returns std::lock_guard<std::mutex>
.
我看了反汇编,发现在 std::lock_guard<std::mutex>(getMutexObj());
的情况下,根本没有相关的操作码。
然而当我把它改成
std::lock_guard<std::mutex>{getMutexObj()};
并查看反汇编,它被编译为临时对象创建但再次没有任何警告。
不同于 23;
消息 "error: statement has no effect",创建一个未命名的临时文件 lock_guard
具有锁定和解锁互斥量的作用。
这具有对其他线程可见的内存屏障的副作用。
守卫是一种聚合数据类型(与您与之比较的原始类型相反),其中构造函数可以包含自定义代码;所以 "statement has no effect" 是(或可能是)不正确的——你的意图很可能只是构造并立即销毁一个对象。 "unused variable" 也不匹配,因为临时对象没有名称并且在块结束之前不会持久存在。
我认为是因为编译器不知道引用变量的确切上下文。引发警告的情况不是引用值,它们是原始类型。编译器不知道您要完成什么,因为它不知道库的作用。
此外,在这种情况下,您实际上是在锁定变量。但它可能是别的东西。就像在你的程序之外开始一些事情。如果你不打算停止你开始的事情,你真的不需要一个名字。
简单的答案是 - 编译器没有发出任何警告,因为这是一个可以(理论上)有意使用的有效表达式。表达式:
std::lock_guard<std::mutex>(getMutexObj());
是类型转换的函数符号,定义在标准中:
5.2.3 Explicit type conversion (functional notation)
1) A simple-type-specifier (7.1.6.2) or typename-specifier (14.6) followed by a parenthesized expression-list constructs a value of the specified type given the expression list. If the expression list is a single expression, the type conversion expression is equivalent (in definedness, and if defined in meaning) to the corresponding cast expression (5.4). (...)
并被解释为静态转换 (5.2.9.4):
An expression e can be explicitly converted to a type T using a static_cast of the form static_cast(e) if the declaration
T t(e);
is well-formed, for some invented temporary variablet
(8.5). The effect of such an explicit conversion is the same as performing the declaration and initialization and then using the temporary variable as the result of the conversion. (...)
编译器无法发出它为
发出的任何警告int(23);
uint16_t remove_me = 23;
因为您没有创建变量(可能未使用)并且表达式 确实 有效果 - 它正在调用 std::lock_guard<std::mutex>
的构造函数。
std::lock_guard<std::mutex>(getMutexObj());
这实际上应该给出一个错误,因为它声明了一个名为 getMutexObj
的函数,但它已经用不同的 return 类型声明了(并且你不能在 return 上重载类型)。这是一个 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
没有帮助,因为 class 的正确使用意味着它是总是 "unused" (你永远不会在构造后引用它)。对于这种情况,需要实施不同的属性和警告。