(void *)1 是什么意思?

48

我正在阅读ROS的代码。

在文件ros_comm/roscpp/include/ros/subscriber.h中,我看到了这样一段代码:

operator void*() const { return (impl_ && impl_->isValid()) ? (void*)1 : (void*)0; }

好的,在C语言中,(void *)0可以被视为NULL,但是(void *)1表示什么呢?

如果一个类Foo包含这个函数,那么我们可以这样编码:

Foo foo;
void *ptr = foo;

对吗?这是不是意味着void *ptr = (void *)1是可能的?这是什么意思?


9
它将1转换为指针,将0转换为指针,就这样。 - Brandon
1
这是一个C风格的转换。因此,它将1转换为void *。至于为什么要这样做,或者这有什么实际用途,请不要问我。如果可以的话,请问原始程序员,或者这里可能有其他人可以阐明这个问题。 - user10957435
它可能用于定位地址为1的内存单元中存储的内容。不过我们不确定,建议向运行该库的人询问。 - lost_in_the_source
13
在现代C++(C++11及以上版本),可能会使用指针作为bool类型的隐式转换,但这不是你应该采用的方式。请注意,此处不提供解释。 - Eljay
我在这里检查了一下:https://github.com/ros/ros_comm/blob/melodic-devel/clients/roscpp/include/ros/subscriber.h#L71 我没有看到任何地方使用强制转换..也许它是隐式使用的,但是要搜索这么多东西..我不知道.. - Brandon
@Brandon 切换分支到 kinetic-devel,然后你就会找到它。 - Yves
2个回答

74

这是一个旧诀窍,用于避免在C++11引入explicit上下文转换之前出现的从其他类型到bool的隐式转换问题。它旨在用于检查有效性:

Subscriber my_subscriber = someFunction();
if (!my_subscriber) {
    // error case
}

重要的一点是没有从void*到整数类型的内置转换,但从bool到整数类型确实存在内置转换。与此同时,从void*bool的内置转换存在。这意味着,如果您定义了一个到bool的隐式转换,那么以下代码将出奇迹地有效:

void my_func(int i);

void another_func() {
    Subscriber sub = something();
    my_func(sub);
}

将转换定义为void*避免了该问题。


然而,这个技巧现在已经过时了。 C++11引入了explicit转换。 将bool显式转换视为if和循环的条件,但不考虑其他有问题的情况。这意味着现在应将这种转换编写为:

explicit operator bool() const { return impl_ && impl_->isValid(); }

尽管在“安全布尔习惯用法”中使用的返回类型通常是指向成员函数的指针,因此误用的可能性甚至更小。 - Deduplicator

-1

这表明编写代码的人对使用的语言或工具不是很熟悉,或者代码已经存在了很长时间,并且被不同的人修改过,可能在过去的某个时候经历了从C到C++的转换,仍然保留着一些遗留的API契约(期望一个void*),这可能会带来麻烦。

如果你查看源代码,就没有什么好的理由这样做。 impl_ 是一个实现了operator boolboost::shared_ptr<Impl>,而Impl::isValid也返回bool。因此,在任何地方都没有理由使用或返回除bool之外的任何东西。

基本上,这是一种扭曲(可能危险)的写法:

return impl_ && impl_->isValid();

似乎 boost::shared_ptrexplicit 要旧一些(参见 https://www.boost.org/doc/libs/1_70_0/libs/smart_ptr/doc/html/smart_ptr.html#history),并且在历史上也使用了安全布尔惯用法(参见 https://www.boost.org/doc/libs/1_31_0/libs/smart_ptr/shared_ptr.htm#conversions)。 - Deduplicator
1
@Deduplicator:确实有点老,但这并不是重点(explicit关键字在这里并没有什么区别,暴露的API函数都没有使用它)。所谓的安全布尔值惯用语是一个误称,更适合称为肮脏的黑客技巧。它绝不安全,也不能很好地表达意图。bool传达的是:“这是一个是或否的值”,而void*传达的是:“我是一个指针,请解引用我”。对于(void*)0(void*)1来说,通常不是一个明智的选择。事实上,当偶然以不同的方式使用时,“它可以工作”的事实是无关紧要的。 - Damon
编程不仅仅是“对我有效”或者“我相信它有效”,而是确保事物被明确定义、表达清晰、无歧义、给出可重复的结果并尽可能提供故障安全性。调用该函数的人必须立即知道如何处理返回值,而不需要查阅20页文档。这在void*中并不适用。概念“是的,但有一个隐式转换到……任何东西”的说法并不准确。 - Damon
没有任何理智的人会否认,安全布尔技巧是一种肮脏的黑客行为,这意味着所有的含义。不幸的是,在 C++11 之前,这也是最好的方法。我只提到了 boost::shared_ptr 的历史,因为虽然你承认正在讨论的代码可能是古老的,但你奇怪地使用了该库的当前版本作为关于所使用的语言版本的论据。 - Deduplicator
@Deduplicator:我必须承认,我不太熟悉20世纪90年代中后期的Boost版本 :-) 虽然我很确定 return !!m_ptr; 在80年代末已经被定义并且完美可靠地工作(除了显然缺少一个 explicit 关键字)。为了防止一个大多数情况下无害、可能且很少发生的布尔值到整型的转换,牺牲清晰度和表现力,这让我感到困惑。 - Damon
好的,在使用中它是非常清晰的。但是,返回类型只是一种难以编写的怪物,并且有时会泄漏到诊断中。此外,据我所知,重载解析是关键场景。好吧,让我们把它扔进垃圾桶吧。 - Deduplicator

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