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

Is there a way to avoid implicit conversion to void*?

我使用的 API 在某些函数中接受 void*。我经常不小心将错误的指针类型传递给函数,当然它编译得很好,但在运行时不起作用。

对于指向某个 class 的指针,有没有办法禁止隐式转换为 void*

来自standard、expr.conv.ptr:

A prvalue of type “pointer to cv T”, where T is an object type, can be converted to a prvalue of type “pointer to cv void”. The pointer value (basic.compound) is unchanged by this conversion.

您不能禁止此转换。

Is there any way to disable implicit conversion to void* for pointers to a certain class?

不,您不能阻止隐式转换,但您可以将 API 函数包装在代理函数中,在编译时检查类型并在那里 approve/disapprove 它们。

示例:

#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"
    );

您可以为 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
}

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

#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*和[之间的往返转换=13=] 或 intptr_t 是安全的。在您的库中,您可以以相反的方式进行转换以检索原始指针。

这个有点人为设计的示例让您可以将特定类型的指针显式转换为句柄,但指针不会隐式转换为句柄。 (虽然,现在,整数值将隐式转换为句柄,并给你未定义的行为。)在现实世界中,应该优化辅助函数。

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

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