Dynamic_cast và static_cast trong C ++


155

Tôi khá bối rối với dynamic_casttừ khóa trong C ++.

struct A {
    virtual void f() { }
};
struct B : public A { };
struct C { };

void f () {
    A a;
    B b;

    A* ap = &b;
    B* b1 = dynamic_cast<B*> (&a);  // NULL, because 'a' is not a 'B'
    B* b2 = dynamic_cast<B*> (ap);  // 'b'
    C* c = dynamic_cast<C*> (ap);   // NULL.

    A& ar = dynamic_cast<A&> (*ap); // Ok.
    B& br = dynamic_cast<B&> (*ap); // Ok.
    C& cr = dynamic_cast<C&> (*ap); // std::bad_cast
}

định nghĩa nói:

Các dynamic_casttừ khóa phôi một dữ liệu từ một con trỏ hoặc tham chiếu loại khác, thực hiện một kiểm tra thời gian chạy để đảm bảo tính hợp lệ của các diễn viên

Chúng ta có thể viết tương đương với dynamic_castC ++ bằng C để tôi có thể hiểu rõ hơn về mọi thứ không?


1
Nếu bạn muốn có được một ý tưởng tốt về cách dynamic_cast<>hoạt động của hậu trường (hoặc bao nhiêu C ++ hoạt động), một cuốn sách hay (cũng khá dễ đọc cho một thứ gì đó quá kỹ thuật) là "Mô hình đối tượng C ++" của Lippman. Ngoài ra, các cuốn sách "Thiết kế và tiến hóa của C ++" và "Ngôn ngữ lập trình C ++" của Stroustrup là những tài nguyên tốt, nhưng cuốn sách của Lippman dành riêng cho cách C ++ hoạt động 'đằng sau hậu trường'.
Michael Burr

Nhận xét trong dòng B* b2 = dynamic_cast<B*> (ap) // 'b'có nghĩa là gì? b2 is pointer to bhay cái gì?
LRDPRDX

@BogdanSikach Câu hỏi đó là gì? Điều đó chỉ có nghĩa là ap bây giờ là một loại B

Câu trả lời:


282

Đây là một danh sách static_cast<>dynamic_cast<>đặc biệt là khi chúng liên quan đến con trỏ. Đây chỉ là một danh sách cấp 101, nó không bao gồm tất cả những điều phức tạp.

static_cast <Loại *> (ptr)

Cái này đưa con trỏ vào ptrvà cố gắng đưa nó vào một con trỏ kiểu một cách an toàn Type*. Diễn viên này được thực hiện tại thời gian biên dịch. Nó sẽ chỉ thực hiện các diễn viên nếu các loại loại có liên quan. Nếu các loại không liên quan, bạn sẽ gặp lỗi trình biên dịch. Ví dụ:

class B {};
class D : public B {};
class X {};

int main()
{
  D* d = new D;
  B* b = static_cast<B*>(d); // this works
  X* x = static_cast<X*>(d); // ERROR - Won't compile
  return 0;
}

Dynamic_cast <Loại *> (ptr)

Điều này một lần nữa cố gắng đưa con trỏ vào ptrvà đưa nó vào một con trỏ kiểu một cách an toàn Type*. Nhưng dàn diễn viên này được thực thi trong thời gian chạy, không biên dịch thời gian. Bởi vì đây là một diễn viên thời gian chạy, nó rất hữu ích đặc biệt khi kết hợp với các lớp đa hình. Trong thực tế, trong các trường hợp certian, các lớp phải là đa hình để các diễn viên hợp pháp.

Các diễn viên có thể đi theo một trong hai hướng: từ cơ sở đến dẫn xuất (B2D) hoặc từ dẫn xuất đến cơ sở (D2B). Nó đủ đơn giản để xem các phôi D2B sẽ hoạt động như thế nào trong thời gian chạy. Hoặc ptrlà có nguồn gốc từ Typehoặc nó không. Trong trường hợp của D2B Dynamic_cast <> s, các quy tắc rất đơn giản. Bạn có thể cố gắng truyền bất cứ thứ gì sang bất cứ thứ gì khác, và nếu ptrtrên thực tế có nguồn gốc từ Type, bạn sẽ nhận được một Type*con trỏ trở lại từ đó dynamic_cast. Nếu không, bạn sẽ nhận được một con trỏ NULL.

Nhưng phôi B2D phức tạp hơn một chút. Hãy xem xét các mã sau đây:

#include <iostream>
using namespace std;

class Base
{
public:
    virtual void DoIt() = 0;    // pure virtual
    virtual ~Base() {};
};

class Foo : public Base
{
public:
    virtual void DoIt() { cout << "Foo"; }; 
    void FooIt() { cout << "Fooing It..."; }
};

class Bar : public Base
{
public :
    virtual void DoIt() { cout << "Bar"; }
    void BarIt() { cout << "baring It..."; }
};

Base* CreateRandom()
{
    if( (rand()%2) == 0 )
        return new Foo;
    else
        return new Bar;
}


int main()
{
    for( int n = 0; n < 10; ++n )
    {
        Base* base = CreateRandom();

            base->DoIt();

        Bar* bar = (Bar*)base;
        bar->BarIt();
    }
  return 0;
}

main()không thể biết loại đối tượng nào CreateRandom()sẽ quay trở lại, vì vậy dàn diễn viên theo phong cách C Bar* bar = (Bar*)base;được quyết định là không an toàn. Làm thế nào bạn có thể khắc phục điều này? Một cách sẽ là thêm một hàm như bool AreYouABar() const = 0;vào lớp cơ sở và trả về truetừ Barfalsetừ Foo. Nhưng có một cách khác: sử dụng dynamic_cast<>:

int main()
{
    for( int n = 0; n < 10; ++n )
    {
        Base* base = CreateRandom();

        base->DoIt();

        Bar* bar = dynamic_cast<Bar*>(base);
        Foo* foo = dynamic_cast<Foo*>(base);
        if( bar )
            bar->BarIt();
        if( foo )
            foo->FooIt();
    }
  return 0;

}

Các phôi thực thi trong thời gian chạy và hoạt động bằng cách truy vấn đối tượng (không cần phải lo lắng về việc làm thế nào bây giờ), hỏi nó nếu nó là loại chúng ta đang tìm kiếm. Nếu có, dynamic_cast<Type*>trả về một con trỏ; nếu không, nó trả về NULL.

Để việc truyền từ cơ sở đến nguồn gốc này hoạt động bằng cách sử dụng dynamic_cast<>, Base, Foo và Bar phải là thứ mà Standard gọi là các kiểu đa hình . Để trở thành một loại đa hình, lớp của bạn phải có ít nhất một virtualhàm. Nếu các lớp của bạn không phải là kiểu đa hình, việc sử dụng từ gốc đến gốc dynamic_castsẽ không biên dịch. Thí dụ:

class Base {};
class Der : public Base {};


int main()
{
    Base* base = new Der;
    Der* der = dynamic_cast<Der*>(base); // ERROR - Won't compile

    return 0;
}

Thêm một chức năng ảo vào cơ sở, chẳng hạn như một dtor ảo, sẽ tạo ra cả hai loại đa hình Base và Der:

class Base 
{
public:
    virtual ~Base(){};
};
class Der : public Base {};


int main()
{
    Base* base = new Der;
    Der* der = dynamic_cast<Der*>(base); // OK

    return 0;
}

9
Tại sao trình biên dịch phàn nàn về nó ở nơi đầu tiên? và không khi chúng ta chỉ cung cấp một dctor ảo cho cơ sở?
Rika

5
Cần lưu ý rằng nếu bạn làm Base* base = new Base;, dynamic_cast<Foo*>(base)sẽ được NULL.
Yay295

2
@ Coderx7 Dynamic_cast cần Thông tin loại thời gian chạy (RTTI) chỉ khả dụng cho các lớp đa hình, tức là các lớp có ít nhất một phương thức ảo.
Elvorfirilmathredia

@ Yay295 Tại sao dynamic_cast<Foo*>(base)là null trong trường hợp a Base* base = new Base;?
MuneshSingh

3
@munesh Vì basekhông phải là a Foo. Một Basecon trỏ có thể trỏ đến một Foo, nhưng nó vẫn là một Foo, vì vậy một diễn viên động sẽ hoạt động. Nếu bạn làm thế Base* base = new Base,base là một Base, không phải a Foo, vì vậy bạn không thể tự động chuyển nó thành a Foo.
Yay295

20

Trừ khi bạn đang thực hiện RTTI cuộn bằng tay của riêng bạn (và bỏ qua hệ thống một), không thể thực hiện dynamic_casttrực tiếp trong mã cấp độ người dùng C ++. dynamic_castđược gắn rất nhiều vào hệ thống RTTI của triển khai C ++.

Nhưng, để giúp bạn hiểu RTTI (và do đó dynamic_cast) nhiều hơn, bạn nên đọc lên <typeinfo>tiêu đề và typeidtoán tử. Điều này trả về thông tin loại tương ứng với đối tượng bạn có trong tay và bạn có thể hỏi những điều khác nhau (có giới hạn) từ những đối tượng thông tin loại này.


Tôi sẽ chỉ cho bạn vào Wikipedia, nhưng các bài viết của nó về RTTI và dynamic_castrất sơ sài. :-P Chỉ cần chơi với chính nó cho đến khi bạn hiểu rõ về nó. :-)
Chris Jester-Young

10

Hơn cả mã trong C, tôi nghĩ rằng một định nghĩa tiếng Anh có thể là đủ:

Cho một lớp Base có lớp dẫn xuất Derogen, dynamic_castsẽ chuyển đổi một con trỏ Base thành một con trỏ Derogen khi và chỉ khi đối tượng thực tế được trỏ vào thực tế là một đối tượng Derogen.

class Base { virtual ~Base() {} };
class Derived : public Base {};
class Derived2 : public Base {};
class ReDerived : public Derived {};

void test( Base & base )
{
   dynamic_cast<Derived&>(base);
}

int main() {
   Base b;
   Derived d;
   Derived2 d2;
   ReDerived rd;

   test( b );   // throw: b is not a Derived object
   test( d );   // ok
   test( d2 );  // throw: d2 is not a Derived object
   test( rd );  // ok: rd is a ReDerived, and thus a derived object
}

Trong ví dụ này, lệnh gọi testliên kết các đối tượng khác nhau để tham chiếu đến Base. Trong nội bộ, tài liệu tham khảo bị hạ thấp thành một tài liệu tham khảo Derivedtheo cách an toàn: loại hình này sẽ chỉ thành công cho những trường hợp mà đối tượng được tham chiếu thực sự là một ví dụ Derived.


2
Tôi nghĩ tốt hơn là làm rõ rằng các ví dụ được chia sẻ ở trên sẽ hoạt động dựa trên các giả định nếu các lớp chỉ đa hình, tức là ít nhất lớp Base có ít nhất một phương thức ảo.
irsis

1
Điều này sẽ thất bại vì các lớp không phải là loại đa hình.
tên người

4

Những điều sau đây không thực sự gần với những gì bạn nhận được từ C ++ dynamic_castvề mặt kiểm tra loại nhưng có lẽ nó sẽ giúp bạn hiểu mục đích của nó tốt hơn một chút:

struct Animal // Would be a base class in C++
{
    enum Type { Dog, Cat };
    Type type;
};

Animal * make_dog()
{
   Animal * dog = new Animal;
   dog->type = Animal::Dog;
   return dog;
}
Animal * make_cat()
{
   Animal * cat = new Animal;
   cat->type = Animal::Cat;
   return cat;
}

Animal * dyn_cast(AnimalType type, Animal * animal)
{
    if(animal->type == type)
        return animal;
    return 0;
}

void bark(Animal * dog)
{
    assert(dog->type == Animal::Dog);

    // make "dog" bark
}

int main()
{
    Animal * animal;
    if(rand() % 2)
        animal = make_dog();
    else
        animal = make_cat();

    // At this point we have no idea what kind of animal we have
    // so we use dyn_cast to see if it's a dog

    if(dyn_cast(Animal::Dog, animal))
    {
        bark(animal); // we are sure the call is safe
    }

    delete animal;
}

3

A dynamic_castthực hiện kiểm tra loại bằng RTTI . Nếu thất bại, nó sẽ ném cho bạn một ngoại lệ (nếu bạn đưa ra tham chiếu) hoặc NULL nếu bạn cho nó một con trỏ.


2

Đầu tiên, để mô tả truyền động theo thuật ngữ C, chúng ta phải biểu diễn các lớp trong C. Các lớp có hàm ảo sử dụng "VTABLE" của các con trỏ tới các hàm ảo. Nhận xét là C ++. Vui lòng định dạng lại và sửa lỗi biên dịch ...

// class A { public: int data; virtual int GetData(){return data;} };
typedef struct A { void**vtable; int data;} A;
int AGetData(A*this){ return this->data; }
void * Avtable[] = { (void*)AGetData };
A * newA() { A*res = malloc(sizeof(A)); res->vtable = Avtable; return res; }

// class B : public class A { public: int moredata; virtual int GetData(){return data+1;} }
typedef struct B { void**vtable; int data; int moredata; } B;
int BGetData(B*this){ return this->data + 1; }
void * Bvtable[] = { (void*)BGetData };
B * newB() { B*res = malloc(sizeof(B)); res->vtable = Bvtable; return res; }

// int temp = ptr->GetData();
int temp = ((int(*)())ptr->vtable[0])();

Sau đó, một diễn viên năng động là một cái gì đó như:

// A * ptr = new B();
A * ptr = (A*) newB();
// B * aB = dynamic_cast<B>(ptr);
B * aB = ( ptr->vtable == Bvtable ? (B*) aB : (B*) 0 );

1
Câu hỏi ban đầu là "Chúng ta có thể viết tương đương với Dynamic_cast của C ++ bằng C" không.
David Rayna

1

Không có lớp nào trong C, vì vậy không thể viết Dynamic_cast bằng ngôn ngữ đó. Các cấu trúc C không có các phương thức (kết quả là chúng không có các phương thức ảo), vì vậy không có gì "động" trong đó.


1

Không, không dễ dàng. Trình biên dịch gán một danh tính duy nhất cho mỗi lớp, thông tin đó được tham chiếu bởi mọi thể hiện đối tượng và đó là những gì được kiểm tra trong thời gian chạy để xác định xem một biểu diễn động có hợp pháp không. Bạn có thể tạo một lớp cơ sở tiêu chuẩn với thông tin này và các toán tử để thực hiện kiểm tra thời gian chạy trên lớp cơ sở đó, sau đó bất kỳ lớp dẫn xuất nào cũng sẽ thông báo cho lớp cơ sở về vị trí của nó trong hệ thống phân cấp lớp và mọi trường hợp của các lớp đó sẽ được truyền qua thời gian chạy hoạt động của bạn.

biên tập

Đây là một triển khai thể hiện một kỹ thuật. Tôi không khẳng định trình biên dịch sử dụng bất cứ thứ gì như thế này, nhưng tôi nghĩ nó thể hiện các khái niệm:

class SafeCastableBase
{
public:
    typedef long TypeID;
    static TypeID s_nextTypeID;
    static TypeID GetNextTypeID()
    {
        return s_nextTypeID++;
    }
    static TypeID GetTypeID()
    {
        return 0;
    }
    virtual bool CanCastTo(TypeID id)
    {
        if (GetTypeID() != id) { return false; }
        return true;
    }
    template <class Target>
    static Target *SafeCast(SafeCastableBase *pSource)
    {
        if (pSource->CanCastTo(Target::GetTypeID()))
        {
            return (Target*)pSource;
        }
        return NULL;
    }
};
SafeCastableBase::TypeID SafeCastableBase::s_nextTypeID = 1;

class TypeIDInitializer
{
public:
    TypeIDInitializer(SafeCastableBase::TypeID *pTypeID)
    {
        *pTypeID = SafeCastableBase::GetNextTypeID();
    }
};

class ChildCastable : public SafeCastableBase
{
public:
    static TypeID s_typeID;
    static TypeID GetTypeID()
    {
        return s_typeID;
    }
    virtual bool CanCastTo(TypeID id)
    {
        if (GetTypeID() != id) { return SafeCastableBase::CanCastTo(id); }
        return true;
    }
};
SafeCastableBase::TypeID ChildCastable::s_typeID;

TypeIDInitializer ChildCastableInitializer(&ChildCastable::s_typeID);

class PeerChildCastable : public SafeCastableBase
{
public:
    static TypeID s_typeID;
    static TypeID GetTypeID()
    {
        return s_typeID;
    }
    virtual bool CanCastTo(TypeID id)
    {
        if (GetTypeID() != id) { return SafeCastableBase::CanCastTo(id); }
        return true;
    }
};
SafeCastableBase::TypeID PeerChildCastable::s_typeID;

TypeIDInitializer PeerChildCastableInitializer(&PeerChildCastable::s_typeID);

int _tmain(int argc, _TCHAR* argv[])
{
    ChildCastable *pChild = new ChildCastable();
    SafeCastableBase *pBase = new SafeCastableBase();
    PeerChildCastable *pPeerChild = new PeerChildCastable();
    ChildCastable *pSameChild = SafeCastableBase::SafeCast<ChildCastable>(pChild);
    SafeCastableBase *pBaseToChild = SafeCastableBase::SafeCast<SafeCastableBase>(pChild);
    ChildCastable *pNullDownCast = SafeCastableBase::SafeCast<ChildCastable>(pBase);
    SafeCastableBase *pBaseToPeerChild = SafeCastableBase::SafeCast<SafeCastableBase>(pPeerChild);
    ChildCastable *pNullCrossCast = SafeCastableBase::SafeCast<ChildCastable>(pPeerChild);
    return 0;
}


0

static_cast< Type* >(ptr)

static_cast trong C ++ có thể được sử dụng trong các tình huống trong đó tất cả các kiểu truyền có thể được xác minh tại thời điểm biên dịch .

dynamic_cast< Type* >(ptr)

Dynamic_cast trong C ++ có thể được sử dụng để thực hiện kiểu truyền xuống an toàn . Dynamic_cast là đa hình thời gian chạy. Toán tử Dynamic_cast, chuyển đổi một cách an toàn từ một con trỏ (hoặc tham chiếu) thành một loại cơ sở thành một con trỏ (hoặc tham chiếu) thành một loại dẫn xuất.

ví dụ 1:

#include <iostream>
using namespace std;

class A
{
public:
    virtual void f(){cout << "A::f()" << endl;}
};

class B : public A
{
public:
    void f(){cout << "B::f()" << endl;}
};

int main()
{
    A a;
    B b;
    a.f();        // A::f()
    b.f();        // B::f()

    A *pA = &a;   
    B *pB = &b;   
    pA->f();      // A::f()
    pB->f();      // B::f()

    pA = &b;
    // pB = &a;      // not allowed
    pB = dynamic_cast<B*>(&a); // allowed but it returns NULL

    return 0;
}

Để biết thêm thông tin bấm vào đây

ví dụ 2:

#include <iostream>

using namespace std;

class A {
public:
    virtual void print()const {cout << " A\n";}
};

class B {
public:
    virtual void print()const {cout << " B\n";}
};

class C: public A, public B {
public:
    void print()const {cout << " C\n";}
};


int main()
{

    A* a = new A;
    B* b = new B;
    C* c = new C;

    a -> print(); b -> print(); c -> print();
    b = dynamic_cast< B*>(a);  //fails
    if (b)  
       b -> print();  
    else 
       cout << "no B\n";
    a = c;
    a -> print(); //C prints
    b = dynamic_cast< B*>(a);  //succeeds
    if (b)
       b -> print();  
    else 
       cout << "no B\n";
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.