有没有办法避免隐式转换为 void*?
Posted
技术标签:
【中文标题】有没有办法避免隐式转换为 void*?【英文标题】:Is there a way to avoid implicit conversion to void*? 【发布时间】:2021-10-10 20:49:27 【问题描述】:我正在使用在某些函数中接受 void*
的 API。我经常不小心将错误的指针类型传递给函数,当然它编译得很好,但在运行时不起作用。
有没有办法为指向某个类的指针禁用隐式转换为void*
?
【问题讨论】:
"有没有办法禁用隐式强制转换为void*
以获取指向某个类的指针?" - 不,没有。所有指针都可以隐式转换为void*
,这是 C++ 语言的核心特性。我可能会包装有问题的 API 函数并让包装器只接受正确类型的指针,然后根据需要将它们传递给 API。
@SamR 这个问题是关于将void*
转换为其他指针类型。这与这个问题相反
这个问题是关于隐式转换。没有隐式强制转换之类的东西。强制转换是您在源代码中编写的内容,用于告诉编译器进行转换。
您可以做的一件事是使用您自己的函数包装 API,从而使传递无效指针成为不可能。
一如既往,示例代码将消除所有误解。
【参考方案1】:
来自standard,expr.conv.ptr:
类型为“pointer to cv
T
”的纯右值,其中T
是一个对象类型,可以转换为“pointer to cv”类型的纯右值>void
”。指针值 (basic.compound) 在此转换中没有改变。
您不能禁止此转换。
【讨论】:
【参考方案2】:有什么方法可以禁止隐式转换为
void*
以获取指向某个类的指针?
不,您无法阻止隐式转换,但您可以将 API 函数包装在代理函数中,在编译时检查类型并在那里批准/拒绝它们。
例子:
#include <iostream>
#include <string>
#include <type_traits>
void api(void* p) // your original API
std::cout << "void* " << p << '\n';
template<class T>
void api_wrapper(T* p) // your wrapper
// let constness fail in the original api instead of in the static_assert:
using type = std::remove_const_t<T>*;
static_assert(
// add your approved types here
std::is_convertible_v<type, std::ostream*> ||
std::is_convertible_v<type, std::string*>,
"Not an approved type"
);
api(p);
int main()
std::string foo;
api_wrapper(&std::cout);
api_wrapper(&foo);
//api_wrapper(&std::cin); // compile time error "Not an approved type"
如果您要拒绝的指针类型集非常小,那么不要在static_assert
中列出所有已批准 类型,只需列出已拒绝的类型并调整布尔逻辑:
static_assert(
// add your disapproved types here
not std::is_convertible_v<type, std::ostream*> &&
not std::is_convertible_v<type, std::string*>,
"Not an approved type"
);
【讨论】:
警告:转换为(此处)std::string *
需要调整的指针将通过您的static_assert
,但在没有调整的情况下转换为void *
,这肯定会失败。
@Quentin 我不喜欢这样的声音,但我不完全理解你在说什么。你介意用this example 来说明吗?
我认为如果你可以用类型 Y (std::string *) 做一些你不能用类型 X (yourtype) 做的事情,你可以将 X 转换为 Y,但是 api 转换type X to type Z (void *),你没有得到 type Y 的功能。
@TedLyngmo 当然,here you go。
公平地说,这个问题有点未明确,特别是我看不到 API 本身如何区分不同的类型,即使它期望它们。我怀疑每个 API 函数实际上都需要一个已知类型。【参考方案3】:
您可以为 API 函数添加已删除的重载:
// API function
void api(void* p)
// ...
// Special case for nullptr
inline void api(std::nullptr_t)
api((void*)nullptr);
// Everything else is disabled
template <class ...T>
void api(T&&... t) = delete;
int main()
int i = 0;
void* p = &i;
api(p); // Compiles
// api(&i); // Doesn't compile
【讨论】:
加上这个,所有的调用可能看起来像api(static_cast<void*>(&i));
而不是api(&i);
。如果 OP 不小心将错误类型的对象传递给函数,这有什么帮助?
@TedLyngmo 据我了解,OP 只想通过 void*
并禁用所有其他类型。我认为 OP 没有一组批准的类型。不过我可能错了。
我明白你的意思,如果你是正确的,这个答案应该是正确的!
这和@TedLyngmo 的答案都非常好。将其标记为正确,因为我发现它最有启发性。碰巧只有一种类型是我想要传递给 API 的,而且只有一种是我不小心传递的。所以我可以做一个非模板版本来阻止错误的类型被传递。但是,我发现 API 虽然不在我的控制范围内,但会响应宏定义以允许我选择特定类型而不是 void* 作为这些函数的参数。所以我正在这样做。非常有趣的答案!【参考方案4】:
如果参数对客户端不透明,您可以将其公开为不会隐式转换的句柄类型,例如
#include <cstdint>
using api_handle = std::uintptr_t;
inline api_handle make_api_handle(const char* p)
return (api_handle)(const void*)p;
inline api_handle make_api_handle(const int* p)
return (api_handle)(const void*)p;
两阶段转换是因为语言标准在技术上只规定任何对象指针和void*
之间的往返转换是安全的,而void*
和uintptr_t
或intptr_t
之间的往返转换是安全的。在您的库中,您可以通过相反的方式进行转换以检索原始指针。
这个有点做作的例子让您可以将特定类型的指针显式转换为句柄,但指针不会隐式转换为句柄。 (尽管现在,整数值将隐式转换为句柄,并为您提供未定义的行为。)在现实世界中,应该优化辅助函数。
如果这种方法适用于您的 API,另一种解决方案是将您的 void*
包装在最小的 struct
中并传递它们。现代编译器应该能够在寄存器中传递它们,就像指针一样。您还可以添加存储类型或其他信息的字段。另一种选择可能是保留有效对象的向量并传递索引,就像 Unix 中的文件句柄一样。
对于构造函数,您可以使用explicit
关键字来禁用参数的所有隐式转换。
【讨论】:
以上是关于有没有办法避免隐式转换为 void*?的主要内容,如果未能解决你的问题,请参考以下文章
避免构造函数中的隐式转换。 'explicit' 关键字在这里没有帮助
无法将类型“void”隐式转换为“bool”Xamarin 警报
公共 bool 方法不能将类型“bool”隐式转换为“void”
ARC 不允许将 Objective-C 指针隐式转换为“void *”