【问题标题】:Is there a way to avoid implicit conversion to void*?有没有办法避免隐式转换为 void*?
【发布时间】:2021-10-10 20:49:27
【问题描述】:

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

有没有办法为指向某个类的指针禁用隐式转换为void*

【问题讨论】:

  • "有没有办法禁用隐式强制转换为 void* 以获取指向某个类的指针?" - 不,没有。所有指针都可以隐式转换为void*,这是 C++ 语言的核心特性。我可能会包装有问题的 API 函数并让包装器只接受正确类型的指针,然后根据需要将它们传递给 API。
  • @SamR 这个问题是关于将void* 转换为其他指针类型。这与这个问题相反
  • 这个问题是关于隐式转换。没有隐式强制转换之类的东西。强制转换是您在源代码中编写的内容,用于告诉编译器进行转换。
  • 您可以做的一件事是使用您自己的函数包装 API,从而使传递无效指针成为不可能。
  • 一如既往,示例代码将消除所有误解。

标签: c++ implicit-conversion void-pointers


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

    【讨论】:

      猜你喜欢
      • 2014-06-16
      • 1970-01-01
      • 2014-11-09
      • 1970-01-01
      • 2019-09-07
      • 1970-01-01
      • 2017-06-08
      • 1970-01-01
      • 2019-05-29
      相关资源
      最近更新 更多