AIL*_*ien 42 c++ void-pointers implicit-conversion
我正在使用void*在某些功能中接受的 API 。我经常不小心将错误的指针类型传递给函数,当然它编译得很好,但在运行时不起作用。
有没有办法禁用void*指向某个类的指针的隐式转换?
Ted*_*gmo 38
有什么方法可以禁用隐式转换
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"
}
Run Code Online (Sandbox Code Playgroud)
如果您要拒绝的指针类型集非常小,那么不要在 中列出所有已批准的类型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"
);
Run Code Online (Sandbox Code Playgroud)
Ayx*_*xan 34
您可以为 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
}
Run Code Online (Sandbox Code Playgroud)
Nat*_*son 21
从标准,expr.conv.ptr:
类型为“指向cv 的 指针”的纯右值
T,其中T是对象类型,可以转换为类型为“指向cv 的 指针”的纯右值void。指针值 ( basic.compound ) 不会因此转换而改变。
您不能禁止这种转换。
如果参数对客户端是不透明的,您可能会将其公开为不会隐式转换的句柄类型,例如
#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;
}
Run Code Online (Sandbox Code Playgroud)
两阶段转换是因为语言标准在技术上只说任何对象指针和之间的往返转换void*是安全的,而void*和uintptr_t或之间的往返转换intptr_t是安全的。在您的库中,您可以以相反的方式进行转换以检索原始指针。
这个有点人为的示例允许您将特定类型的指针显式转换为句柄,但指针不会隐式转换为句柄。(尽管现在,整数值将隐式转换为句柄,并为您提供未定义的行为。)在现实世界中,辅助函数应该被优化掉。
如果这种方法适用于您的 API,另一种解决方案是将您的 API 封装void*在一个最小值中struct并传递它们。现代编译器应该能够在寄存器中传递它们,就像指针一样。您还可以添加存储类型或其他信息的字段。另一种选择可能是保留有效对象的向量并将索引传递给它,例如 Unix 中的文件句柄。
对于构造函数,您可以使用explicit关键字来禁用参数的所有隐式转换。