【问题标题】:How to check if a void* is to a pointer on a valid instance of an object type?如何检查 void* 是否指向对象类型的有效实例上的指针?
【发布时间】:2014-01-01 02:08:54
【问题描述】:

我正在寻找最常见和最可靠的方法来检查 void* 是否可以在给定的 C++ 对象类型中转换。您可以在下面看到有关上下文的一些信息。

当我为 DLL 定义 C API 时,我经常使用 void* 来隐藏我在后面使用的 C++ 对象(如下所示)

typedef void* Instance_t;
int createInstance(Instance_t* pInst);
int processing(Instance_t inst, uint8_t* pBuf, size_t bufLength);

当我createInstance代码cast这样的指针时:

int createInstance(Instance_t* pInst)
{ 
   MyClass* ptr = new MyClass();
   *pInst = (void*)(ptr);
   //.... etc
   return 0;
 }

但问题是我们如何在稍后定义的所有其他 C 函数中检查我们收到的 void* 值是否是有效 MyClass*。我认为我们不能,因为在这种情况下,没有一个 C++ 强制转换运算符是真正类型安全的(即使是 dynamic_cast)。

目前我最好的解决方案是使用 C 转换(或 reinterpret_cast),如果一切正常,则调用 MyClass 定义的 IsValid 函数。

你有更好的方法来做这个检查吗?

【问题讨论】:

  • 你怎么知道数据是有效的?你在脑海中应用了什么一致性测试?将其应用于收到的数据,如果它通过数据可能会很好。
  • 转发声明你的类型并且只在你的*.c源中定义它。这避免了 void * 的使用,因为您需要类型检查
  • 如果你对 RTTI 没问题,你可以perhaps do something like you wish
  • @RogerRowland 这仅适用于指向多态类型的指针,不适用于 void*
  • @MikeSeymour 是的,这是真的,对不起,它需要一个共同的基础而不是 void*,我讨厌 CObject 类型的方法。

标签: c++ c object pointers dll


【解决方案1】:

你不能这样做,除非你(比如说)从一个内存池中分配你所有的 MyClass 实例,并检查你传递的地址是指向那个内存池的指针。或者维护一个有效实例列表。

但是,如果您需要传递一个不透明的指针,只需让客户端使用

struct MyClass;
typedef struct MyClass *Instance_t;

这将编译干净,让您放心。只要您只使用指针,编译器就会很高兴。当它取消引用它时,编译器需要知道指针实际指向的内容。

【讨论】:

  • 我相信正确的C 代码应该是typedef struct MyClass * Instance_t
【解决方案2】:

我认为你不能这样做,我认为你不应该这样做。 void * 只是指向内存中某个位置的指针。几乎按照定义,没有办法知道它指向什么。

但是,为什么要将所有内容类型转换为 void *,如果您想防止用户摆弄您类的内部结构,为什么不在您的类上使用受保护的和私有的方法?

【讨论】:

  • 哼...你如何在 C 中使用 protectedprivate
  • 嗯...我们在这里谈论的是 C++。您也不能在 C 中使用 class,或在 C 中使用任何对象...
  • @craigmj 他说的是 C api。
  • 澄清一下:OP 正在询问如何构建他的 C API,因此提出的解决方案需要在提供的公共 API 中容纳 C,尽管可能在它之外使用 C++。
  • 啊,和你一起。所以他在 C API 中返回一个void*,然后在稍后收到它时,想确保它是指向他/她自己的类的指针。所以他/她不必担心保护类方法,因为 C API 可能是对方法的唯一访问。但它并没有更接近确定指针......我猜答案的第一部分留下了一个;-)
【解决方案3】:

没有办法检查无类型指针是否指向任何特定类型的有效对象。如果你使用void*,你就抛弃了类型检查。相反,您可以在 C 标头中声明类型,并使用指向该(不完整)类型的指针而不是 void*

struct Instance;
int createInstance(struct Instance** pInst);
int processing(struct Instance* inst, uint8_t* pBuf, size_t bufLength);

然后在C++中,就可以定义和使用类了。

// Probably better to use "struct" rather than "class" in case
// some compilers complain about mixing class keys.
struct Instance {
    // Whatever
};

int createInstance(Instance** pInst) {
    *pInst = new Instance;
    // and so on
}

【讨论】:

  • 注意:MSVC 对struct Instanceclass Instance 的处理方式不同,C++ 标准建议不要将struct/class 混合用于同一类型。
  • @MatthieuM.:我从未听说过这个建议。但是,如果某些编译器在这方面被破坏,那么在这两种情况下最好坚持struct
  • 我不会说坏了:§7.1.6.3/3 [dcl.type.elab]详细说明中存在的 class-key 或 enum 关键字-类型说明符应与详细说明的类型说明符中的名称所指的声明实物一致。 [...] 并且 class 或 struct class-key 应用于引用使用 class 或 struct class-key 声明的类(第 9 条)。; classstruct 是否可以互换并不完全清楚。
  • @MatthieuM.:但该段还指定了“实物同意”的含义:“要么 class struct 类-key 用于引用使用classstruct 类键声明的类。这对我来说似乎很清楚。如果它必须完全匹配,它会这样说,就像 unionenum 一样。
【解决方案4】:

无法确定 void* 是否指向有效的 C++ 类。除非您启用了 RTTI,否则没有与类关联的元数据,即使这样,在 C++ 中也有很多情况,void* 没有指向一个类。例如:

int x=10;
void *ptr = &x;

这里ptr 指向一个原始值。没有与整数关联的 RTTI,所以您如何查询它以确定任何内容>

【讨论】:

    【解决方案5】:

    当我需要将一些对象从我的 CPP 库导出到 C 代码时,我会这样做:

    typedef void * OBJ1;
    typedef void * OBJ2;
    
    OBJ1 createObj1();
    OBJ2 createObj2();
    
    void doObj1(OBJ1 obj);
    

    所以在 do 函数中我完全知道期望什么对象

    【讨论】:

    • 这或多或少是问题所描述的。但是,正如问题所说,如果您使用的是void*(的别名),则无法防止像do(createObj2()); 这样的错误。 (另外,你不能调用函数do,因为那是一个关键字)。
    • 你说的很对,“do”是保留的我有点着急,我纠正了我的答案。所以像 doObj1(createObj2()); 这样的错误可能是因为没有办法严格检查 typedef 类型。但至少这是我所知道的避免混淆的最佳方式
    • 我觉得这个typedef void * OBJ1; 很混乱。而对于编译器来说,这根本没有帮助。
    • @alk 比较一下: doObj1( void * o ); doObj2( 无效 * o );还有这个 doObj1(obj1_t o); doObj2(obj2_t o)。您也可以在 typedef 中使用“struct”关键字(typedef struct obj1 * obj1_t),这样 doObj1(createObj1()) 将在编译时失败。
    • 后者说得通,前者假装存却不是。
    【解决方案6】:

    简短的回答:这可能很困难。

    问题很多,但基本上归结为 C 和 C++ 中可访问的操作的非常低级别的性质(注意:可访问,但不一定合法)。 void* 的作用是任何指针都可以被强制指向它,但是如果您使用其他类型,滥用reinterpret_cast 仍然可能导致麻烦。


    一个简单的解决方案是使用标记。本质上,在指针中放置一个类型 id,以便您始终可以知道对象的原始类型。这很繁重(因为每种类型都需要修改),但很容易部署。

    typedef enum {
        SP_ATag,
        SP_BTag,
        SP_CTag,
        ...
    } SP_Tag_t;
    
    // External Tag
    typedef struct {
        SP_Tag_t tag;
        void* p;
    } SP_Any_t;
    
    // Internal Tag
    struct A {
        SP_Tag_t tag;
        ...;
    };
    
    ...
    
    typedef union {
        A* a;
        B* b;
        C* c;
    } SP_Any_t;
    

    然后,您使用SP_Any_t,而不是使用void*

    优势

    • 轻量级
    • 外部解决方案不需要修改现有类
    • 内部解决方案应与现有 C 代码二进制兼容

    缺点

    • 一个地方声明所有类型
    • 破坏标签很容易(无论是有意还是无意)

    一个更复杂的解决方案是引入一个每个类型的注册表,这可能是一个很好的调试帮助。缺点是您需要检测现有类型以使其工作,但它仍然很容易,而且它涉及更多的运行时开销。但是,嘿:it works

    template <typename> class Registrable;
    
    //
    // class Registry
    //
    class Registry {
    public:
    template <typename> friend class Registrable;
    
    template <typename T>
    static T* Cast(void*);
    
    private:
    struct TagType {};
    
    using Key = std::pair<TagType const*, void*>;
    using Store = std::set<Key>;
    
    template <typename T>
    static void Register(Registrable<T>* t);
    
    template <typename T>
    static void Unregister(Registrable<T>* t);
    
    static Store& Get();
    }; // class Registry
    
    template <typename T>
    T* Registry::Cast(void* const pointer) {
    TagType const* const tag = &Registrable<T>::Tag;
    
    if (Get().count(std::make_pair(tag, pointer)) == 0) { return nullptr; }
    
    return static_cast<T*>(reinterpret_cast<Registrable<T>*>(pointer));
    }
    
    template <typename T>
    void Registry::Register(Registrable<T>* t) {
    TagType const* const tag = &T::Tag;
    void* const pointer = reinterpret_cast<void*>(t);
    
    Get().insert(std::make_pair(tag, pointer));
    }
    
    template <typename T>
    void Registry::Unregister(Registrable<T>* t) {
    TagType const* const tag = &T::Tag;
    void* const pointer = reinterpret_cast<void*>(t);
    
    Get().erase(std::make_pair(tag, pointer));
    }
    
    Registry::Store& Registry::Get() { static Store S; return S; }
    
    //
    // class Registrable
    //
    template <typename T>
    class Registrable {
    public:
    static Registry::TagType const Tag;
    
    Registrable();
    ~Registrable();
    
    Registrable(Registrable&&) = default;
    Registrable& operator=(Registrable&&) = default;
    
    Registrable(Registrable const&) = default;
    Registrable& operator=(Registrable const&) = default;
    }; // class Registrable
    
    template <typename T> Registry::TagType const Registrable<T>::Tag;
    
    template <typename T>
    Registrable<T>::Registrable() { Registry::Register(this); }
    
    template <typename T>
    Registrable<T>::~Registrable() { try { Registry::Register(this); } catch(...) {} }
    

    【讨论】:

      【解决方案7】:

      不要使用指针而是使用句柄

      • 如果内存在 dll 端,则不要传递指针而是句柄 仅限您的对象
      • 如果您的对象的内存由应用程序分配并传递给 dll,则保留这些指针的表并将它们也视为句柄

      这适用于单个 dll。当然,如果来自 1.dll 的指针通过应用程序传递给 2.dll,则它不起作用。在这种情况下,无论如何你都在最薄的冰上。

      【讨论】:

        【解决方案8】:

        我有一个使用 RTTI 有一些限制的解决方案...

        如果您的实例都派生自一个虚拟基类,那么您可以安全地重新解释转换为该基类,然后动态转换为您的其他类...

        class Object
        {
           virtual ~Object() {}
        };
        
        class A : public Object
        {
          static bool IsOfThisClass(void *data)
          { 
             return dynamic_cast<A*>((Object*)data) != 0;
          }
        }
        

        如果 someData 属于 A 类,则调用 A::IsOfThisClass(someData) 将返回 true。

        这不是您想向用户公开的那种 hack,因为它仅在 void* 指向从 Object 派生的类时才有效,但它在受控情况下可能是一个有用的构建块。

        【讨论】:

          猜你喜欢
          • 2013-10-24
          • 1970-01-01
          • 2013-08-20
          • 2012-11-24
          • 2011-05-04
          • 2013-05-18
          • 2011-12-18
          • 1970-01-01
          • 2019-05-04
          相关资源
          最近更新 更多