如何检查void *是否指向对象类型的有效实例上的指针?

时间:2013-12-13 10:32:32

标签: c++ c object pointers dll

我正在寻找最常见且最健壮的方法来检查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 cast(或reinterpret_cast),如果一切正常,调用IsValid函数定义MyClass。

您有更好的方法进行检查吗?

8 个答案:

答案 0 :(得分:7)

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

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

struct MyClass;
typedef struct MyClass *Instance_t;

这将干净利落地编译,给您一定的安心。只要您只使用指针,编译器就会很高兴。它解引它时,编译器需要知道指针实际指向的是什么。

答案 1 :(得分:5)

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

但是为什么要将所有内容转换为void *,为什么不在课堂上使用受保护和私有方法,如果你想阻止用户摆弄你班级的内部?

答案 2 :(得分: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
}

答案 3 :(得分:1)

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

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

此处ptr指向原始值。没有RTTI与整数相关联,因此您如何查询它以确定任何>

答案 4 :(得分:0)

当我需要将CPP lib中的某些对象导出到C代码时,我会这样做:

typedef void * OBJ1;
typedef void * OBJ2;

OBJ1 createObj1();
OBJ2 createObj2();

void doObj1(OBJ1 obj);

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

答案 5 :(得分:0)

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

问题很多,但基本上归结为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;

然后,您使用void*

,而不是使用SP_Any_t

优点

  • 轻质
  • 外部解决方案不需要修改现有类
  • 内部解决方案应与现有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(...) {} }

答案 6 :(得分:0)

不要使用指针而是处理

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

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

答案 7 :(得分:0)

我有一个使用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(s​​omeData)将返回true。

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