C++ 陈雷蒙';s的单例实现使用了不可靠的强制转换?

C++ 陈雷蒙';s的单例实现使用了不可靠的强制转换?,c++,visual-c++,singleton,reinterpret-cast,C++,Visual C++,Singleton,Reinterpret Cast,在此链接中: 最近有人向我指出以下几点: Widget *pwidOld = reinterpret_cast<Widget*> (InterlockedCompareExchangePointerRelease( &reinterpret_cast<PVOID&>(g_pwidCached), pwid, NULL)); Widget*pwidO

在此链接中:

最近有人向我指出以下几点:

Widget *pwidOld = reinterpret_cast<Widget*>
                 (InterlockedCompareExchangePointerRelease(
                  &reinterpret_cast<PVOID&>(g_pwidCached),
                  pwid, NULL));
Widget*pwidOld=reinterpret\u cast
(联锁比较交换点删除)(
&重新解释铸造(g_pwidCached),
pwid,NULL);
有一个良性和一个严重的问题

好的方面是可以对返回类型进行静态转换

严重的问题似乎是:

&reinterpret_cast<PVOID&>(g_pwidCached)
&重新解释强制转换(g\u pwidCached)
有人告诉我,使用严格的别名,当您将&(void*&)g_pwidCached传递给函数时,编译器可以假设g_pwidCached的值没有改变,因为改变将通过一个指针类型发生,该指针类型不是对象的类型,也不是char*(因为g_pwidCached不是void*,而是小部件*)。3.10/10似乎是相关的


这只是一个特定编译器实现的函数,它只是VisualC++保证了该行正确工作?

代码当然依赖于实现特定属性。甚至不能保证
Widget*
的大小与
void*
的大小相同,更不用说当传递指向
Widget*
void**
时,一些名为
InterlocatedCompareExchangePointerRelease
的函数将正常工作

我可能忽略了一些东西,但我认为实际问题是,“优化器中的引用转义代码是否会看到
重新解释\u cast
,并假设没有对
gpwidCached
的引用转义函数?”如果答案是“是”,那么我们就有问题了,因为编译器在实际发生修改时不会假设任何修改。但答案是“否”,前提是它将
InterlocatedCompareeExchangePointerRelease
视为一个黑盒,因为据它所知,函数在访问点之前会将点转换回正确的类型,在这种情况下,编译器无法自由地假设没有修改发生

[编辑:事实上,答案更是“不”我一开始就意识到了这一点。大概
g_pwidCached
是一个全局的,因此编译器永远不能假设它没有被它调用的任何未知代码修改,而不管参数是什么。该代码可能会使用名称
g_pwidCached
来修改它,当然这将具有正确的类型以避免出现别名。]

如果
InterlocatedCompareeExchangePointerRelease
是内联的和/或作为编译器内部实现的,则答案也是“否”,因为实现将(如果正确)执行任何必要的操作,以确保不会出错。请注意,该函数采用了一个
void*volatile*
,因此无论如何实现,它都必须执行特定于实现的操作,以确保没有别名问题,因为传递类型双关指针是预期的用例

有人告诉我,当你通过时,使用严格的别名 &(void*&)g_pwiddcached到函数中,允许编译器 假设g_pwidCached的值没有改变,因为 更改将通过不是该类型的指针类型发生 对象的属性,并且不是字符*


那不太正确。如果函数确实通过不正确的类型访问值,则行为是未定义的。毫无疑问,Windows实现确实如此。但是在没有看到函数的定义的情况下,编译器不知道它是否通过它作为传递的类型访问它,或者不知何故找出正确的类型将其转换回,以便在不违反严格别名的情况下进行访问。这就是为什么(正如我上面所说)编译器不能对未知函数的调用进行任何严格的依赖别名的优化。

gpwidcached的类型是错误的,应该是错误的

void* g_pwidCached;
因为唯一使用它的东西,将它视为
void*

(可以说,
delete g_pwidCached;
无论如何都是错误的,应该通过
InterlockedExchangePointer(nullptr,&g_pwidCached)
将值放入本地并使用本地命令删除)


但所有这些,包括更好的代码,都在那篇博文的评论中提到了。

代码当然依赖于特定于实现的属性。甚至不能保证
Widget*
的大小与
void*
相同,更不用说当传递指向
Widget*
void**
时,一些名为
InterlocatedCompareExchangePointerRelease
的函数将正常工作:大多数代码通常是:Widget*wid;ICEPR((无效**)和wid,…);我相信这也不正确,对吗?史蒂夫杰索普,就C++标准而言。但这是非常特定于Windows的代码,Posix和(我想)Windows都要求所有指针(包括Posix中指向函数的指针)具有相同的大小和表示形式。@Dilip:我认为它们同样正确,详细信息请参见我的答案。@JamesKanze:当然。但问题的一部分是,“这是特定于平台的代码吗”,因此我认为值得一提的是,答案是“显然是的”:-)非常感谢您确认我的怀疑。如果你要重新编写那一行代码,那么什么才是“最安全的”和符合标准的编写方式?@Dilip:我不会重写它,它本来就可以。以Windows为目标的编译器只需完成这项工作。感谢您的详细解释。你对这些东西的控制让我感到羞愧:-)我就是这么做的,但Jessop先生觉得,因为这是一个特定于平台的代码,无论如何,VC++将保证它所需要的任何特定于实现的属性。