Menemukan jenis objek di C ++


147

Saya memiliki kelas A dan kelas lain yang mewarisi darinya, B. Saya mengganti fungsi yang menerima objek tipe A sebagai parameter, jadi saya harus menerima A. Namun, saya kemudian memanggil fungsi yang hanya dimiliki oleh B, jadi saya ingin mengembalikan false dan tidak melanjutkan jika objek yang dilewati bukan dari tipe B.

Apa cara terbaik untuk mengetahui tipe objek yang diteruskan ke fungsi saya?

Jawaban:


162

dynamic_cast harus melakukan triknya

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

Itu dynamic_cast kunci melempar datum dari satu penunjuk atau tipe referensi ke yang lain, melakukan pemeriksaan runtime untuk memastikan validitas pemeran.

Jika Anda mencoba melakukan cast ke pointer ke tipe yang bukan tipe objek aktual, hasil dari cast akan NULL. Jika Anda mencoba melakukan cast untuk merujuk ke tipe yang bukan tipe objek aktual, para pemain akan melemparkan bad_castpengecualian.

Pastikan setidaknya ada satu fungsi virtual di kelas Base untuk membuat dynamic_cast berfungsi.

Topik Wikipedia Informasi jenis waktu berjalan

RTTI hanya tersedia untuk kelas yang polimorfik, yang berarti mereka memiliki setidaknya satu metode virtual. Dalam praktiknya, ini bukan batasan karena kelas dasar harus memiliki destruktor virtual untuk memungkinkan objek kelas turunan melakukan pembersihan yang tepat jika dihapus dari pointer basis.


1
Apa yang Anda maksud dengan harus ada fungsi virtual di kelas Base untuk membuat dynamic_cast berfungsi. Bagi saya itu penting, saya hanya akan menebak.
GiCo

3
OK menemukannya: Run-Time Type Information (RTTI) hanya tersedia untuk kelas yang polimorfik, yang berarti mereka memiliki setidaknya satu metode virtual. dynamic_cast dan typeid perlu RTTI.
GiCo

Tidak dynamic_castmelempar jika tidak dapat dikonversi? Apakah ada cara untuk melakukannya tanpa menghasilkan lemparan?
jww

A* aptr = dynamic_cast<A*>(ptr);// Bukankah seharusnya seperti ini
Mehdi Karamosly

Apakah ini berfungsi untuk POD yang telah dilemparkan ke uint8_t*? Artinya, saya bisa memeriksa bahwa uint32_t* x = dynamic_cast<uint32_t*>(p), di mana padalah uint8_t*? (Saya mencoba menemukan tes untuk menghukum pelanggaran).
jww

157

Pemain dinamis adalah yang terbaik untuk deskripsi masalah Anda, tetapi saya hanya ingin menambahkan bahwa Anda dapat menemukan jenis kelas dengan:

#include <typeinfo>

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

4
Bagus jika Anda benar-benar tidak tahu apa objek Anda. Jawaban yang diterima mengasumsikan Anda tahu.
unludo

4
@xus Ya. itu adalah bagian dari tajuk std
Amey Jah

8
Saya tidak mengerti caranya. Ketik nama id tidak diperlukan untuk menjadi berguna dan implementasi didefinisikan.
Sepatu

3
Paling menarik di sini: Nama instance dari kelas yang sama tidak harus sama. Namun typeid itu sendiri harus membandingkan dengan yang sama untuk instance dari kelas yang sama, lihat stackoverflow.com/questions/1986418/typeid-versus-typeof-in-c
FourtyTwo

1
Catatan gcc mengembalikan nama yang diarsip misalnya 11MyClass. Untuk membatalkan, Anda dapat menggunakan perpustakaan ekstensi ABI di cxxabi.h. Ini memberi Anda abi::__cxa_demangleyang akan memberi Anda nama asli
David G

27

Ini disebut RTTI , tetapi Anda hampir pasti ingin mempertimbangkan kembali desain Anda di sini, karena menemukan jenis dan melakukan hal-hal khusus berdasarkan itu membuat kode Anda lebih rapuh.


4
Benar. Sayangnya saya sedang mengerjakan proyek yang sudah ada jadi saya tidak bisa mengubah desainnya, atau apa pun di kelas A.
lemnisca

11

Untuk melengkapi, saya akan membangun Robocide dan menunjukkan bahwa typeiddapat digunakan sendiri tanpa menggunakan nama ():

#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;
}

Keluaran:

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

9

Mungkin menanamkan ke dalam ID Anda "tag" dan menggunakannya untuk membedakan antara objek kelas A dan objek kelas B.

Namun ini menunjukkan kelemahan dalam desain. Idealnya metode-metode dalam B yang tidak dimiliki A, harus menjadi bagian dari A tetapi dibiarkan kosong, dan B menimpa mereka. Ini menghilangkan kode kelas khusus dan lebih dalam semangat OOP.



4

Karena kelas Anda bukan polimorfik. Mencoba:

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

Sekarang BaseClasadalah polimorfik. Saya mengubah kelas menjadi struct karena anggota suatu struct adalah publik secara default.


3

Deskripsi Anda sedikit membingungkan.

Secara umum, meskipun beberapa implementasi C ++ memiliki mekanisme untuk itu, Anda tidak seharusnya bertanya tentang jenisnya. Sebagai gantinya, Anda seharusnya melakukan dynamic_cast pada pointer ke A. Apa yang akan dilakukan adalah saat runtime, isi aktual dari pointer ke A akan diperiksa. Jika Anda memiliki B, Anda akan mendapatkan pointer ke B. Jika tidak, Anda akan mendapatkan pengecualian atau null.


1
Perlu dicatat Anda akan mendapatkan pengecualian hanya jika Anda melakukan referensi yang gagal. yaitu dynamic_cast <T &> (t). Gips pointer gagal mengembalikan NULL. ie dynamic_cast <T *> (t)
Alfaifax

Ya, saya seharusnya mengklarifikasi hal itu dengan lebih baik. Terima kasih. Saya berharap ada kata yang dideskripsikan dalam tipe C yang merupakan referensi bukan nilai.
Uri

3

Seperti yang ditunjukkan orang lain, Anda dapat menggunakan dynamic_cast. Tetapi umumnya menggunakan dynamic_cast untuk mengetahui jenis kelas turunan yang Anda kerjakan menunjukkan desain yang buruk. Jika Anda meng-override fungsi yang mengambil pointer A sebagai parameter maka itu harus dapat bekerja dengan metode / data kelas A itu sendiri dan tidak boleh bergantung pada data kelas B. Dalam kasus Anda, alih-alih menimpa jika Anda yakin bahwa metode yang Anda tulis hanya akan bekerja dengan kelas B, maka Anda harus menulis metode baru di kelas B.


1

Gunakan fungsi kelebihan beban. Tidak memerlukan dynamic_cast atau bahkan dukungan 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
    }
};

Kanan dari pertanyaan awal: "Saya nanti memanggil fungsi yang hanya dimiliki B" - bagaimana kelebihan beban akan bekerja dalam kasus seperti itu?
Marcin Gil

Saat Anda memanggil Bar dengan A, tidak ada hal-hal B yang terjadi. Saat Anda memanggil Bar dengan B, metode yang hanya ada di B dapat dipanggil. Apakah Anda membaca pertanyaan aslinya? Bar adalah miliknya "Saya mengganti fungsi yang menerima objek tipe A sebagai parameter"
jmucchiello

7
Ini tidak bekerja dengan polimorfisme dinamis, yang saya curigai si penanya gunakan. C ++ tidak dapat memilih overload berdasarkan kelas runtime dari parameter, hanya berdasarkan pada tipe waktu kompilasi.
Steve Jessop

1

Jika Anda dapat mengakses perpustakaan tambahan, mungkin fungsi type_id_with_cvr () adalah yang Anda butuhkan, yang dapat memberikan tipe data tanpa menghapus const, volatile, & dan && modifier . Berikut adalah contoh sederhana dalam 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;
}

Semoga ini bermanfaat.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.