การค้นหาประเภทของวัตถุใน C ++


147

ฉันมีคลาส A และคลาสอื่นที่สืบทอดมาจาก B. ฉันกำลังแทนที่ฟังก์ชันที่ยอมรับวัตถุประเภท A เป็นพารามิเตอร์ดังนั้นฉันต้องยอมรับ A อย่างไรก็ตามในภายหลังฉันเรียกใช้ฟังก์ชันที่มี B เท่านั้น ดังนั้นฉันต้องการกลับเท็จและไม่ดำเนินการต่อหากวัตถุที่ส่งผ่านไม่ใช่ประเภท B

วิธีที่ดีที่สุดในการค้นหาประเภทของวัตถุที่ส่งผ่านไปยังฟังก์ชั่นของฉันคืออะไร?

คำตอบ:


162

dynamic_cast ควรทำเคล็ดลับ

TYPE& dynamic_cast<TYPE&> (object);
TYPE* dynamic_cast<TYPE*> (object);

dynamic_castคำหลักปลดเปลื้องตัวเลขจากตัวชี้หรือการอ้างอิงประเภทหนึ่งไปยังอีกที่มีประสิทธิภาพการตรวจสอบรันไทม์เพื่อให้มั่นใจความถูกต้องของตัวละคร

หากคุณพยายามที่จะส่งตัวชี้ไปยังชนิดที่ไม่ใช่วัตถุจริงผลลัพธ์ของการส่งจะเป็น NULL หากคุณพยายามที่จะร่ายอ้างอิงถึงประเภทที่ไม่ใช่วัตถุจริงนักแสดงจะโยนbad_castข้อยกเว้น

ตรวจสอบให้แน่ใจว่ามีอย่างน้อยหนึ่งฟังก์ชั่นเสมือนจริงในคลาสฐานเพื่อให้ dynamic_cast ทำงาน

ข้อมูลวิกิพีเดียหัวข้อประเภทเวลาทำงาน

RTTI พร้อมใช้งานสำหรับคลาสที่ polymorphic เท่านั้นซึ่งหมายความว่ามีวิธีเสมือนอย่างน้อยหนึ่งวิธี ในทางปฏิบัติสิ่งนี้ไม่ใช่ข้อ จำกัด เนื่องจากคลาสพื้นฐานต้องมี destructor เสมือนเพื่ออนุญาตให้วัตถุของคลาสที่ได้รับมาทำการล้างข้อมูลที่เหมาะสมหากถูกลบออกจากตัวชี้พื้นฐาน


1
คุณหมายถึงอะไรต้องมีฟังก์ชั่นเสมือนจริงในคลาส Base เพื่อให้ dynamic_cast ทำงานได้ ที่สำคัญสำหรับฉันดูเหมือนว่าฉันเพิ่งจะเดา
GiCo

3
ตกลงพบว่า: ข้อมูลชนิดรันไทม์ (RTTI) พร้อมใช้งานสำหรับคลาสที่เป็น polymorphic เท่านั้นซึ่งหมายความว่ามีวิธีเสมือนอย่างน้อยหนึ่งวิธี dynamic_cast และ typeid ต้องการ RTTI
GiCo

ไม่dynamic_castโยนถ้ามันไม่สามารถแปลงได้? มีวิธีการทำโดยไม่สร้างการโยนหรือไม่?
jww

A* aptr = dynamic_cast<A*>(ptr);// ไม่ควรที่จะเป็นแบบนี้
Mehdi Karamosly

มันใช้งานได้กับ PODs ที่ถูกส่งไปยัง a uint8_t*หรือไม่? นั่นคือผมสามารถตรวจสอบว่าuint32_t* x = dynamic_cast<uint32_t*>(p)ที่pเป็นuint8_t*? (ฉันกำลังพยายามหาการทดสอบสำหรับการละเมิดที่น่าจับตามอง)
jww

157

การส่งไดนามิกนั้นดีที่สุดสำหรับคำอธิบายปัญหาของคุณ แต่ฉันต้องการเพิ่มว่าคุณสามารถค้นหาประเภทคลาสด้วย:

#include <typeinfo>

...
string s = typeid(YourClass).name()

4
ดีถ้าคุณไม่รู้จริง ๆ ว่าวัตถุของคุณคืออะไร คำตอบที่ยอมรับจะถือว่าคุณทำ
รวม

4
@xus ใช่ มันเป็นส่วนหนึ่งของส่วนหัวมาตรฐาน
Amey Jah

8
ฉันไม่เห็นวิธี ชื่อ id ประเภทไม่จำเป็นต้องมีประโยชน์และมีการใช้งาน
รองเท้า

3
สิ่งที่น่าสนใจที่สุดที่นี่: ชื่อของอินสแตนซ์ของคลาสเดียวกันไม่จำเป็นต้องเท่ากัน อย่างไรก็ตามตัวพิมพ์เองนั้นต้องทำการเปรียบเทียบอย่างเท่าเทียมกันสำหรับอินสแตนซ์ของคลาสเดียวกันดูstackoverflow.com/questions/1986418/typeid-versus-typeof-in-c
FourtyTwo

1
หมายเหตุ GCC 11MyClassผลตอบแทนที่ได้ชื่อเช่น เพื่อ unmangle คุณสามารถใช้ห้องสมุดขยาย ABI cxxabi.hใน สิ่งนี้ให้คุณabi::__cxa_demangleซึ่งจะให้ชื่อจริงแก่คุณ
David G

27

สิ่งนี้เรียกว่าRTTIแต่คุณเกือบจะต้องการพิจารณาการออกแบบของคุณที่นี่อีกครั้งเพราะการค้นหาประเภทและการทำสิ่งพิเศษโดยทำให้โค้ดของคุณเปราะมากขึ้น


4
จริง แต่น่าเสียดายที่ฉันกำลังทำงานในโครงการที่มีอยู่ดังนั้นฉันไม่สามารถเปลี่ยนการออกแบบหรืออะไรก็ได้ในคลาส A.
lemnisca

11

เพื่อให้เสร็จสมบูรณ์ฉันจะสร้าง build จาก Robocide และชี้ให้เห็นว่าtypeidสามารถใช้คนเดียวโดยไม่ต้องใช้ชื่อ ():

#include <typeinfo>
#include <iostream>

using namespace std;

class A {
public:
    virtual ~A() = default; // We're not polymorphic unless we
                            // have a virtual function.
};
class B : public A { } ;
class C : public A { } ;

int
main(int argc, char* argv[])
{
    B b;
    A& a = b;

    cout << "a is B: " << boolalpha << (typeid(a) == typeid(B)) << endl;
    cout << "a is C: " << boolalpha << (typeid(a) == typeid(C)) << endl;
    cout << "b is B: " << boolalpha << (typeid(b) == typeid(B)) << endl;
    cout << "b is A: " << boolalpha << (typeid(b) == typeid(A)) << endl;
    cout << "b is C: " << boolalpha << (typeid(b) == typeid(C)) << endl;
}

เอาท์พุท:

a is B: true
a is C: false
b is B: true
b is A: false
b is C: false

9

อาจฝัง "ID" แท็ก "ในวัตถุของคุณและใช้เพื่อแยกความแตกต่างระหว่างวัตถุของคลาส A และวัตถุของคลาส B

อย่างไรก็ตามสิ่งนี้แสดงข้อบกพร่องในการออกแบบ โดยหลักการแล้ววิธีการเหล่านี้ใน B ที่ A ไม่มีควรเป็นส่วนหนึ่งของ A แต่ปล่อยว่างไว้และ B จะเขียนทับพวกมัน สิ่งนี้ไม่ได้อยู่ที่รหัสเฉพาะของคลาสและเป็นมากกว่าใน OOP



4

เพราะชั้นเรียนของคุณไม่ใช่ polymorphic ลอง:

struct BaseClas { int base; virtual ~BaseClas(){} };
class Derived1 : public BaseClas { int derived1; };

ตอนนี้BaseClasคือ polymorphic ฉันเปลี่ยนคลาสเป็น struct เนื่องจากสมาชิกของ struct เป็นแบบสาธารณะโดยค่าเริ่มต้น


3

คำอธิบายของคุณค่อนข้างสับสน

โดยทั่วไปแล้วแม้ว่าการใช้งาน C ++ บางอย่างจะมีกลไกสำหรับมัน แต่คุณไม่ควรถามเกี่ยวกับประเภทนี้ คุณควรจะทำ dynamic_cast บนตัวชี้ไปที่ A แทนสิ่งนี้จะทำที่รันไทม์เนื้อหาจริงของตัวชี้ไปยัง A จะถูกตรวจสอบ หากคุณมี B คุณจะได้ตัวชี้ไปที่ B ไม่เช่นนั้นคุณจะได้รับข้อยกเว้นหรือค่า Null


1
ควรสังเกตว่าคุณจะได้รับข้อยกเว้นเฉพาะเมื่อคุณทำการส่งนักแสดงอ้างอิงซึ่งล้มเหลว ie dynamic_cast <T &> (t) ตัวชี้การคาสต์ล้มเหลวส่งคืนค่า NULL ie dynamic_cast <T *> (t)
AlfaZulu

ใช่ฉันควรชี้แจงให้ดีขึ้น ขอบคุณ ฉันหวังว่าจะมีคำอธิบายในประเภท C ที่มีการอ้างอิงมากกว่าโดยค่า
Uri

3

ตามที่คนอื่นระบุว่าคุณสามารถใช้ dynamic_cast แต่โดยทั่วไปแล้วการใช้ dynamic_cast เพื่อค้นหาประเภทของคลาสที่ได้รับที่คุณกำลังทำอยู่บ่งบอกถึงการออกแบบที่ไม่ดี หากคุณกำลังเอาชนะฟังก์ชั่นที่ใช้ตัวชี้ของ A เป็นพารามิเตอร์ดังนั้นมันควรจะสามารถทำงานกับวิธีการ / ข้อมูลของคลาส A ได้และไม่ควรขึ้นอยู่กับข้อมูลของคลาส B ในกรณีของคุณแทนที่จะเอาชนะถ้าคุณ แน่ใจว่าวิธีที่คุณเขียนจะใช้ได้กับคลาส B เท่านั้นจากนั้นคุณควรเขียนวิธีการใหม่ในคลาส B


1

ใช้ฟังก์ชั่นโอเวอร์โหลด ไม่ต้องการ dynamic_cast หรือแม้กระทั่งการสนับสนุน RTTI:

class A {};
class B : public A {};

class Foo {
public:
    void Bar(A& a) {
        // do something
    }
    void Bar(B& b) {
        Bar(static_cast<A&>(b));
        // do B specific stuff
    }
};

ได้จากคำถามเดิม: "ฉันโทรหาฟังก์ชั่นที่มีเพียง B ในภายหลัง" - การทำงานมากเกินไปจะทำงานได้อย่างไรในกรณีเช่นนี้?
Marcin Gil

เมื่อคุณเรียกบาร์ด้วย A จะไม่มีสิ่ง B เกิดขึ้น เมื่อคุณเรียก Bar ด้วย a B วิธีการที่มีอยู่ใน B เท่านั้นที่สามารถเรียกได้ คุณอ่านคำถามต้นฉบับหรือไม่ Bar คือเขา "ฉันกำลังเอาชนะฟังก์ชั่นที่รับอ็อบเจ็กต์ประเภท A เป็นพารามิเตอร์"
jmucchiello

7
สิ่งนี้ใช้ไม่ได้กับไดนามิกหลายรูปแบบซึ่งฉันสงสัยว่าผู้ถามกำลังใช้งานอยู่ C ++ ไม่สามารถเลือกโอเวอร์โหลดตามคลาสรันไทม์ของพารามิเตอร์, ขึ้นอยู่กับประเภทเวลารวบรวม
Steve Jessop

1

หากคุณสามารถเข้าถึงห้องสมุดเพิ่มอาจจะtype_id_with_cvr ()ฟังก์ชั่นคือสิ่งที่คุณต้องการซึ่งสามารถให้ประเภทข้อมูลโดยไม่ต้องถอดconst ระเหยและการปรับเปลี่ยนและ นี่คือตัวอย่างง่ายๆใน C ++ 11:

#include <iostream>
#include <boost/type_index.hpp>

int a;
int& ff() 
{
    return a;
}

int main() {
    ff() = 10;
    using boost::typeindex::type_id_with_cvr;
    std::cout << type_id_with_cvr<int&>().pretty_name() << std::endl;
    std::cout << type_id_with_cvr<decltype(ff())>().pretty_name() << std::endl;
    std::cout << typeid(ff()).name() << std::endl;
}

หวังว่านี่จะเป็นประโยชน์

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.