关于c ++:安全检查变量的类型

Safely checking the type of a variable

对于系统,我需要将指针转换为long类型,然后再将long类型转换为指针类型。如您所料,这是非常不安全的。我想做的是使用dynamic_cast进行转换,因此,如果我将它们混合在一起,将会得到一个空指针。该页面显示http://publib.boulder.ibm.com/infocenter/lnxpcomp/v7v91/index.jsp?topic=/com.ibm.vacpp7l.doc/language/ref/clrc05keyword_dynamic_cast.htm

The dynamic_cast operator performs
type conversions at run time. The
dynamic_cast operator guarantees the
conversion of a pointer to a base
class to a pointer to a derived class,
or the conversion of an lvalue
referring to a base class to a
reference to a derived class. A
program can thereby use a class
hierarchy safely. This operator and
the typeid operator provide run-time
type information (RTTI) support in
C++.

我想得到一个错误,如果它为null,那么我写了自己的动态演员表

1
2
3
4
5
template<class T, class T2> T mydynamic_cast(T2 p)
{
    assert(dynamic_cast< T >(p));
    return reinterpret_cast< T >(p);
}

使用MSVC时,出现错误"错误C2681:'long':dynamic_cast的无效表达式类型"。事实证明,这仅适用于具有虚拟功能的类……WTF!我知道动态类型转换的目的是针对上/下类型转换继承问题,但我也认为这是动态解决类型转换问题。我知道我可以使用reinterpret_cast,但是不能保证相同的安全性。

我应该使用什么检查我的类型转换是否为相同类型?我可以比较两个typeid,但是当我想将派生类型转换为它的基数时会遇到问题。那么我该如何解决呢?


dynamic_cast只能在通过继承关联的类之间使用。要将指针转换为long或反之,可以使用reinterpret_cast。要检查指针是否为空,可以assert(ptr != 0)。但是,通常不建议使用reinterpret_cast。为什么需要将指针转换为long?

另一种选择是使用联合:

1
2
3
4
union  U {
int* i_ptr_;
long l;
}

同样,很少也需要工会。


在以仅支持C接口的语言编写的应用程序中加载C ++ DLL时,我不得不做类似的事情。这是一个解决方案,如果传入了意外的对象类型,将立即给您带来错误。这可以使发生问题时更容易诊断。

诀窍在于,您作为句柄传递的每个类都必须从通用基类继承。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
#include <stdexcept>
#include <typeinfo>
#include <string>
#include <iostream>
using namespace std;


// Any class that needs to be passed out as a handle must inherit from this class.
// Use virtual inheritance if needed in multiple inheritance situations.
class Base
{

public:
    virtual ~Base() {} // Ensure a v-table exists for RTTI/dynamic_cast to work
};


class ClassA : public Base
{

};

class ClassB : public Base
{

};

class ClassC
{
public:
    virtual ~ClassC() {}
};

// Convert a pointer to a long handle.  Always use this function
// to pass handles to outside code.  It ensures that T does derive
// from Base, and that things work properly in a multiple inheritance
// situation.
template <typename T>
long pointer_to_handle_cast(T ptr)
{
    return reinterpret_cast<long>(static_cast<Base*>(ptr));
}

// Convert a long handle back to a pointer.  This makes sure at
// compile time that T does derive from Base.  Throws an exception
// if handle is NULL, or a pointer to a non-rtti object, or a pointer
// to a class not convertable to T.
template <typename T>
T safe_handle_cast(long handle)
{
    if (handle == NULL)
        throw invalid_argument(string("Error casting null pointer to") + (typeid(T).name()));

    Base *base = static_cast< T >(NULL); // Check at compile time that T converts to a Base *
    base = reinterpret_cast<Base *>(handle);
    T result = NULL;

    try
    {
        result = dynamic_cast< T >(base);
    }
    catch(__non_rtti_object &)
    {
        throw invalid_argument(string("Error casting non-rtti object to") + (typeid(T).name()));
    }

    if (!result)
        throw invalid_argument(string("Error casting pointer to") + typeid(*base).name() +" to" + (typeid(T).name()));

    return result;
}

int main()
{
    ClassA *a = new ClassA();
    ClassB *b = new ClassB();
    ClassC *c = new ClassC();
    long d = 0;


    long ahandle = pointer_to_handle_cast(a);
    long bhandle = pointer_to_handle_cast(b);
    // long chandle = pointer_to_handle_cast(c); //Won't compile
    long chandle = reinterpret_cast<long>(c);
    // long dhandle = pointer_to_handle_cast(&d); Won't compile
    long dhandle = reinterpret_cast<long>(&d);

    // send handle to library
    //...
    // get handle back
    try
    {
        a = safe_handle_cast<ClassA *>(ahandle);
        //a = safe_handle_cast<ClassA *>(bhandle); // fails at runtime
        //a = safe_handle_cast<ClassA *>(chandle); // fails at runtime
        //a = safe_handle_cast<ClassA *>(dhandle); // fails at runtime
        //a = safe_handle_cast<ClassA *>(NULL); // fails at runtime
        //c = safe_handle_cast<ClassC *>(chandle); // Won't compile
    }
    catch (invalid_argument &ex)
    {
        cout << ex.what() << endl;
    }

    return 0;
}


请记住,在Windows 64中,指针将是64位数量,但long仍将是32位数量,并且代码已损坏。至少,您需要根据平台选择整数类型。我不知道MSVC是否支持uintptr_t,这是C99中提供的用于保存指针的类型。如果有的话,那将是最好的选择。

至于其余的,其他人已经充分说明了dynamic_castreinterpret_cast的原因和原因。


另外,最好使用size_t而不是long -我认为可以确保此类型与地址空间的大小兼容。


您想做的事情听起来像是一个非常糟糕和危险的主意,但是如果您必须这样做(即您正在使用旧系统或在永远不会改变的硬件上工作),那么我建议将指针包装在一些一种简单的结构,包含两个成员:1)指向对象实例的void指针以及一个字符串,枚举或某种其他种类的唯一标识符,这些标识符将告诉您将原始void *强制转换为什么。这是我的意思的示例(注意:我没有打扰过测试,因此其中可能存在语法错误):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct PtrWrapper {
  void* m_theRealPointer;
  std::string m_type;
};

void YourDangerousMethod( long argument ) {

   if ( !argument )
     return;

   PtrWrapper& pw = *(PtrWrapper*)argument;

   assert( !pw.m_type.empty() );

   if ( pw.m_type =="ClassA" ) {
     ClassA* a = (ClassA*)pw.m_theRealPointer;
     a->DoSomething();
   } else if (...) { ... }

}

reinterpret_cast是在此处使用的正确转换。

这几乎是它唯一可以安全执行的操作。

从指针类型到指针T并返回原始指针类型的reinterpret_cast会产生原始指针。 (假设T是至少与原始指针类型一样大的指针或整数类型)

注意,未指定从指针类型到T的reinterpret_cast。 T类型的值没有任何保证,除了如果您随后将其reinterpret_cast转换回原始类型,则可以得到原始值。因此,假设您不尝试对中间long值进行任何操作,那么reinterpret_cast是非常安全且可移植的。

编辑:当然,如果您在第二次转换时不知道原始类型是什么,这将无济于事。那样的话,你就被搞砸了。 long不能以任何方式携带有关从其转换指针的类型信息。


您可以使用reinterpret_cast强制转换为整数类型并返回指针类型。如果整数类型足够大以存储指针值,则该转换将不会更改指针值。

就像其他人已经说过的那样,在非多态类上使用dynamic_cast并不是定义好的行为(除非您进行向上转换,否则无论如何都是隐式的,在这里将被忽略),它也仅适用于指针或引用。不在整数类型上。

最好使用在各种posix系统上找到的::intptr_t。您可以将该类型用作转换为的中间类型。

关于检查转换是否成功,可以使用sizeof:

1
BOOST_STATIC_ASSERT(sizeof(T1) >= sizeof(T2));

如果无法完成转换,将在编译时失败。或者继续在该条件下使用断言,它将在运行时断言。

警告:这不会阻止您使用U以外的其他类型将T*转换为intptr_t并返回到U*。因此,这仅保证在您从...转换时,转换不会更改指针的值。 T*intptr_t并返回到T*。 (感谢Nicola指出您可能会希望获得另一种保护)。


dynamic_cast<>是强制转换,仅可用于可转换类型(在多态意义上)。强制将pointer强制转换为long(litb正确建议static_assert以确保大小的兼容性),所有有关指针类型的信息都将丢失。无法实现safe_reinterpret_cast<>来获取指针:值和类型。

为了澄清我的意思:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct a_kind {};
struct b_kind {};

void function(long ptr)
{}

int
main(int argc, char *argv[])
{
    a_kind * ptr1 = new a_kind;
    b_kind * ptr2 = new b_kind;

    function( (long)ptr1 );
    function( (long)ptr2 );

    return 0;
}

function()不能确定传递的指针的类型,并且无法"向下"将其强制转换为正确的类型,除非以下任何一种情况:

  • long由带有某些类型信息的对象包装。
  • 类型本身在引用的对象中编码。

两种解决方案都很丑陋,应避免使用,因为RTTI是替代方案。


一旦您决定将指针指向长整数,就立即将类型安全性扔给了风。

dynamic_cast用于转换衍生树。即,从基类指针到派生类指针。如果你有:

1
2
3
4
5
6
7
8
9
10
11
class Base
{
};

class Foo : public  Base
{
};

class Bar : public Base
{
};

您可以通过这种方式使用dynamic_cast ...

1
2
3
4
5
6
7
Base* obj = new Bar;

Bar* bar = dynamic_cast<Bar*>(obj); // this returns a pointer to the derived type because obj actually is a 'Bar' object
assert( bar != 0 );

Foo* foo = dynamic_cast<Foo*>(obj);  // this returns NULL because obj isn't a Foo
assert( foo == 0 );

...但是您不能使用动态类型转换将其强制转换到派生树之外。为此,您需要reinterpret_cast或C样式强制转换。