假设我们将一些数据作为一个字节序列获得,并希望将该序列重新解释为一个结构(有一些保证数据确实是正确的格式)。 例如: 现在magic_reinterpret实际上是坏的,因为它打破了严格的别名规则,从而导致UB。 如何实现它不会导致UB而不是像memcpy那样做任何数据副本? ...
提示:本站收集StackOverFlow近2千万问答,支持中英文搜索,鼠标放在语句上弹窗显示对应的参考中文或英文, 本站还提供 中文繁体 英文版本 中英对照 版本,有任何建议请联系yoyou2525@163.com。
刚刚发现阴险崩溃的原因是编译器不加思索的狂野演员 ,无视类型。 这是预期的行为还是编译器错误?
问题 :当涉及类型定义时,可以进行隐式重新解释转换,从而破坏类型系统。
#include <iostream>
template<class A, class B>
inline bool
isSameObject (A const& a, B const& b)
{
return static_cast<const void*> (&a)
== static_cast<const void*> (&b);
}
class Wau
{
int i = -1;
};
class Miau
{
public:
uint u = 1;
};
int
main (int, char**)
{
Wau wau;
using ID = Miau &;
ID wuff = ID(wau); // <<---disaster
std::cout << "Miau=" << wuff.u
<< " ref to same object: " <<std::boolalpha<< isSameObject (wau, wuff)
<< std::endl;
return 0;
}
我很震惊地发现gcc-4.9,gcc-6.3和clang-3.8接受此代码没有任何错误并产生以下输出:
Miau=4294967295 ref to same object: true
请注意我使用类型构造函数语法ID(wau)
。 我希望在C风格的演员(ID)wau
上有这样的行为,即(ID)wau
。 只有在使用新式花括号语法ID{wau}
我们ID{wau}
得到预期的错误...
~$ g++ -std=c++11 -o aua woot.cpp
woot.cpp: In function ‘int main(int, char**)’:
woot.cpp:31:21: error: no matching function for call to ‘Miau::Miau(<brace-enclosed initializer list>)’
ID wuff = ID{wau};
^
woot.cpp:10:7: note: candidate: constexpr Miau::Miau()
class Miau
^~~~
woot.cpp:10:7: note: candidate expects 0 arguments, 1 provided
woot.cpp:10:7: note: candidate: constexpr Miau::Miau(const Miau&)
woot.cpp:10:7: note: no known conversion for argument 1 from ‘Wau’ to ‘const Miau&’
woot.cpp:10:7: note: candidate: constexpr Miau::Miau(Miau&&)
woot.cpp:10:7: note: no known conversion for argument 1 from ‘Wau’ to ‘Miau&&’
不幸的是,由于std::initializer_list
fiasco,大括号语法通常是模板重码中的禁忌。 所以对我来说这是一个严重的问题,因为类型系统的保护在这里有效地破坏了。
可以进行隐式重新解释转换,破坏类型系统。
ID wuff = ID(wau);
这不是一个“隐含的”重新诠释。 这是一种显式类型转换。 虽然转换确实重新解释的事实确实不容易看到。 具体来说,强制转换的语法称为“功能样式”。
如果您不确定哪种类型的强制类型转换(无论是使用函数语法还是C样式语法)执行,那么您应该避免使用它。 许多人认为永远不应该使用显式类型转换。
如果您使用的是static_cast
,那么您将保持在类型系统的保护范围内:
ID wuff = static_cast<ID>(wau);
error: non-const lvalue reference to type 'Miau' cannot bind to a value of unrelated type 'Wau'
简单地依赖隐式转换通常也是安全的:
ID wuff = wau;
error: non-const lvalue reference to type 'Miau' cannot bind to a value of unrelated type 'Wau'
这是预期的行为吗?
是。
还是编译错误?
没有。
要成为完全语言律师, T(expression)
是expression
结果到T
1的转换 。 此转换与调用类的构造函数2有效 。 这就是为什么我们倾向于调用非显式构造函数,只使用一个参数作为转换构造函数 。
using ID = Miau &;
ID wuff = ID(wau);
这相当于对ID
的强制转换表达式 。 由于ID
不是类类型,因此会发生C样式转换。
有人可以解释这种行为背后的原因吗?
我真的不知道为什么它曾经是C ++的一部分。 这不是必需的。 这是有害的。
它是某种向后兼容性(再次,叹息)?
不一定,使用C ++ 11到C ++ 20,我们已经看到了重大变化。 这可以在某一天删除,但我怀疑它会。
1)
[expr.type.conv]
- simple-type-specifier或typename-specifier后跟带括号的可选表达式列表或braced-init-list (初始化程序),在给定初始化程序的情况下构造指定类型的值。 [...]
- 如果初始值设定项是带括号的单个表达式,则类型转换表达式等效于相应的强制转换表达式 。 [...]
2) (当T
是类类型并且存在这样的构造函数时)
[class.ctor]/2
构造函数用于初始化其类类型的对象。 因为构造函数没有名称,所以在名称查找期间永远不会找到它们; 但是,使用函数表示法([expr.type.conv])的显式类型转换将导致调用构造函数来初始化对象。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.