有没有办法避免隐式转换为 void*?

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)

  • 公平地说,这个问题有点不明确,特别是我不明白 API 本身如何区分不同的类型,即使它期望它们。我怀疑 API 的每个函数实际上都需要一个已知类型。 (4认同)
  • 注意:转换为(此处)`std::string *` 需要调整的指针将通过您的`static_assert`,但在没有调整的情况下转换为`void *`,这肯定会失败。 (2认同)

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)

  • @TedLyngmo 据我所知,OP 只想传递 `void*` 并禁用所有其他类型。我不认为 OP 有一组批准的类型。不过我可能错了。 (5认同)
  • 这和@TedLyngmo 的答案都非常好。将此标记为正确,因为我发现它最有启发性。碰巧的是,我真正想传递给 API 的类型只有一种,而我不小心传递了另一种类型。所以我可以做一个非模板版本来阻止传递错误的类型。但是,我发现 API 虽然不在我的控制范围内,但会响应宏定义以允许我选择特定类型而不是 void* 作为这些函数的参数。所以我正在这样做。非常有趣的答案! (5认同)
  • 我明白你的意思,如果你是对的,这个答案应该是正确的! (3认同)
  • 有了这个添加,所有调用可能看起来像 `api(static_cast&lt;void*&gt;(&amp;i));` 而不是 `api(&amp;i);`。如果 OP 不小心将错误类型的对象传递给函数,如何这有帮助吗? (2认同)

Nat*_*son 21

标准,expr.conv.ptr:

类型为“指向cv 的 指针”的纯右值T,其中T是对象类型,可以转换为类型为“指向cv 的 指针”的纯右值void。指针值 ( basic.compound ) 不会因此转换而改变。

您不能禁止这种转换。


Dav*_*lor 5

如果参数对客户端是不透明的,您可能会将其公开为不会隐式转换的句柄类型,例如

#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关键字来禁用参数的所有隐式转换。