有没有办法避免隐式转换为 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&lt;void*&gt;(&amp;i));而不是api(&amp;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_tintptr_t 之间的往返转换是安全的。在您的库中,您可以通过相反的方式进行转换以检索原始指针。

这个有点做作的例子让您可以将特定类型的指针显式转换为句柄,但指针不会隐式转换为句柄。 (尽管现在,整数值将隐式转换为句柄,并为您提供未定义的行为。)在现实世界中,应该优化辅助函数。

如果这种方法适用于您的 API,另一种解决方案是将您的 void* 包装在最小的 struct 中并传递它们。现代编译器应该能够在寄存器中传递它们,就像指针一样。您还可以添加存储类型或其他信息的字段。另一种选择可能是保留有效对象的向量并传递索引,就像 Unix 中的文件句柄一样。

对于构造函数,您可以使用explicit 关键字来禁用参数的所有隐式转换。

【讨论】:

以上是关于有没有办法避免隐式转换为 void*?的主要内容,如果未能解决你的问题,请参考以下文章

避免构造函数中的隐式转换。 'explicit' 关键字在这里没有帮助

无法将类型“void”隐式转换为“bool”Xamarin 警报

公共 bool 方法不能将类型“bool”隐式转换为“void”

ARC 不允许将 Objective-C 指针隐式转换为“void *”

为啥在我的 Web 服务中调用此方法时出现错误“无法将类型 'void' 隐式转换为 'string'?

隐式类类型转换以及如何避免隐式转换