reinterpret_cast是否会导致未定义行为?

14

我有一个类模板 A,其中包含指针容器(T*):

template <typename T>
class A {
public:
   // ... 
private:
   std::vector<T*> data;
};

还有一堆像这样的函数:

void f(const A<const T>&);
void g(const A<const T>&);

这些函数可以通过将A<const T>转换为A<T>来调用吗?
A<double> a;
... 
auto& ac = reinterpret_cast<const A<const double>&>(a);
f(ac);

我非常确定这段代码存在未定义的行为。

在现实生活中使用这种转换是否危险?


7
如果是UB,那在现实生活中使用它们肯定是危险的。不过这听起来很像一个XY问题。 - erip
5
依赖UB总是很危险的,即使*"在现实世界中可以工作"*. 只需想想像Qt-5、Chromium和KDevelop这样的项目,由于使用了一些this == nullptr荒谬的代码,它们在gcc6中被破坏。 - Baum mit Augen
@Holt 可能是一个不可改变的接口。 - erip
你应该阅读标准中的 5.2.10 Reinterpret cast 部分:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/n4582.pdf - Jesper Juhl
@erip我已经更新了问题。您有解决问题的建议吗? - Sergei
@Holt 这个方法可以同时使用 A<const T>A<T> 调用。我不能只保留 A<T> 而放弃 A<const T>,否则就会失去 const-correctness。 - Sergei
2个回答

10
尽管 reinterpret_cast 本身可能是未指定的行为,但一旦进行转换后尝试访问参数,则行为将变得未定义。

N3337 [basic.lval]/10: 如果程序尝试通过非以下类型之一的 glvalue 访问对象的存储值,则行为将变得未定义:

— 对象的动态类型,

— 对象的动态类型的 cv 限定版本,

— 类型与对象的动态类型相似(如 4.4 中所定义),

— 是与对象的动态类型对应的有符号或无符号类型,

— 是与对象的动态类型的 cv 限定版本对应的已签名或未签名类型,

— 包括其中一个上述类型在其元素或非静态数据成员中的聚合体或联合体(包括递归地子聚合体或包含联合体的元素或非静态数据成员),

— 对象的动态类型的(可能带有 cv 限定符的)基类类型,

— char 或 unsigned char 类型。

你的示例不属于上述任何一种情况。

调用任何非静态成员函数都会违反[class.mfct.non-static]/2,即使它不访问类成员。如果所得到的引用以任何方式使用,则几乎在所有方面都是未定义行为。 - T.C.

8
作为不相关类型的>和>,这实际上是未指定的行为(最初我认为是未定义的),因此在现实生活中使用是不好的想法:您永远不知道要移植到哪些系统或编译器可能会以奇怪的方式更改行为。

参考:

5.2.10/11:

如果可以使用reinterpret_cast将T1类型的lvalue表达式显式转换为“对T2的引用”类型,则可以将其转换为“对T2的引用”类型。也就是说,引用强制转换reinterpret_cast(x)与内置的&和*运算符具有相同的效果(类似地,reinterpret_cast(x)也是如此)。

所以他们将我们重定向到了早期的第5.2.10/7部分:

可以将对象指针显式转换为不同类型的对象指针。... ...将prvalue从类型“指向T1的指针”转换为类型“指向T2的指针”(其中T1和T2是对象类型,并且T2的对齐要求不严于T1的对齐)并返回到其原始类型将产生原始指针值。任何其他此类指针转换的结果都是未指定的。

如果f和g是针对容器工作的算法,则简单的解决方案是将它们更改为在范围(迭代器对)上工作的模板算法。


我同意,但在这种情况下,我还没有看到标准中关于UB的任何内容。你有参考资料吗? - erip
4
它违反了严格别名规则,对吧?所以即使这两个的布局相同,它仍然是未定义行为。 - TartanLlama
4
我认为根据[basic.lval]/10这个章节,这仍然是未定义行为(至少如果原作者继续访问参数)。 - TartanLlama
@MarkB 我就怕出现这种情况。我已经更新了问题。你能对此提出一些建议吗? - Sergei

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接