Apakah ada penggunaan yang sah untuk void *?

87

Apakah ada penggunaan yang sah void*di C ++? Atau apakah ini diperkenalkan karena C memilikinya?

Sekadar rekap pikiran saya:

Masukan : Jika kita ingin mengizinkan beberapa jenis masukan, kita dapat membebani fungsi dan metode, sebagai alternatif kita dapat menentukan kelas dasar umum, atau templat (terima kasih telah menyebutkan ini dalam jawaban). Dalam kedua kasus, kode menjadi lebih deskriptif dan lebih sedikit rawan kesalahan (asalkan kelas dasar diimplementasikan dengan cara yang waras).

Hasil : Saya tidak dapat memikirkan situasi apa pun di mana saya lebih suka menerima void*daripada sesuatu yang berasal dari kelas dasar yang dikenal.

Hanya untuk memperjelas apa yang saya maksud: Saya tidak secara khusus menanyakan apakah ada kasus penggunaan void*, tetapi jika ada kasus di mana void*adalah pilihan terbaik atau satu-satunya yang tersedia. Yang telah dijawab dengan sempurna oleh beberapa orang di bawah ini.

magu_
sumber
3
bagaimana dengan saat Anda ingin memiliki beberapa tipe seperti int & std :: string?
Amir
12
@Amir, variant, any, ditandai serikat. Apa pun yang dapat memberi tahu Anda jenis konten yang sebenarnya dan lebih aman untuk digunakan.
Revolver_Ocelot
15
"C has it" adalah pembenaran yang cukup kuat, tidak perlu mencari lebih banyak. Menghindari sebisa mungkin adalah hal yang baik dalam kedua bahasa.
n. 'kata ganti' m.
1
Hanya satu hal: interop dengan C-style API terasa canggung tanpanya.
Martin James
1
Penggunaan yang menarik adalah penghapusan tipe untuk vektor pointer
Paolo M

Jawaban:

81

void*setidaknya diperlukan sebagai hasil dari ::operator new(juga setiap operator new...) dan dari mallocdan sebagai argumen newoperator penempatan .

void*dapat dianggap sebagai supertipe umum dari setiap jenis penunjuk. Jadi ini tidak berarti penunjuk ke void, tapi penunjuk ke apapun.

BTW, jika Anda ingin menyimpan beberapa data untuk beberapa variabel global yang tidak terkait, Anda dapat menggunakan beberapa std::map<void*,int> score; kemudian, setelah mendeklarasikan global int x;dan double y;dan std::string s;melakukan score[&x]=1;dan score[&y]=2;danscore[&z]=3;

memset menginginkan a void* alamat (yang paling umum)

Selain itu, sistem POSIX memiliki dlsym dan jenis kembaliannya jelas seharusnyavoid*

Basile Starynkevitch
sumber
1
Itu poin yang sangat bagus. Saya lupa menyebutkan bahwa saya lebih memikirkan dari sisi pengguna bahasa daripada sisi implementasi. Satu hal lagi yang saya tidak mengerti: Apakah fungsi baru? Saya menganggapnya lebih sebagai kata kunci
magu_
9
new adalah operator, seperti + dan sizeof.
Joshua
7
Tentu saja ingatan bisa dikembalikan melalui char *juga. Mereka sangat dekat dalam aspek ini, meski artinya sedikit berbeda.
edmz
@Joshua. Tidak tahu itu. Terima kasih telah mengajari saya ini. Karena C++tertulis di C++sini adalah alasan yang cukup untuk dimiliki void*. Harus menyukai situs ini. Ajukan satu pertanyaan, belajar banyak.
magu_
3
@black Kembali di hari tua, C bahkan tidak memiliki sebuah void*tipe. Semua fungsi perpustakaan standar digunakanchar*
Cole Johnson
28

Ada beberapa alasan untuk menggunakan void*, 3 alasan paling umum:

  1. berinteraksi dengan pustaka C menggunakan void*di antarmukanya
  2. jenis-penghapusan
  3. menunjukkan memori yang tidak diketik

Dalam urutan terbalik, menunjukkan memori yang tidak diketik dengan void*(3) sebagai ganti char*(atau varian) membantu mencegah aritmatika penunjuk yang tidak disengaja; hanya ada sedikit operasi yang tersedia void*sehingga biasanya memerlukan casting sebelum berguna. Dan tentu saja, char*tidak ada masalah dengan aliasing.

Type-erasure (2) masih digunakan di C ++, bersama dengan template atau tidak:

  • kode non-generik membantu mengurangi penggelembungan biner, ini berguna di jalur dingin bahkan dalam kode generik
  • kode non-generik terkadang diperlukan untuk penyimpanan, bahkan dalam wadah generik seperti std::function

Dan jelas, ketika antarmuka yang Anda tangani menggunakan void*(1), Anda tidak punya banyak pilihan.

Matthieu M.
sumber
15

Oh ya. Bahkan di C ++ terkadang kita pergi dengan void *daripada template<class T*>karena terkadang kode tambahan dari ekspansi template terlalu berat.

Biasanya saya akan menggunakannya sebagai implementasi aktual dari tipe, dan tipe template akan mewarisi darinya dan membungkus gips.

Selain itu, pengalokasi slab khusus (implementasi baru operator) harus digunakan void *. Ini adalah salah satu alasan mengapa g ++ menambahkan ekstensi yang mengizinkan penunjuk aritmatik void *seolah-olah berukuran 1.

Joshua
sumber
Terima kasih atas jawaban Anda. Anda benar, saya lupa menyebutkan template. Tapi tidak akan template akan lebih baik cocok untuk tugas sejak jarang memperkenalkan hukuman kinerja (? Stackoverflow.com/questions/2442358/... )
magu_
1
Templat memperkenalkan hukuman ukuran kode, yang juga merupakan hukuman kinerja dalam banyak kasus.
Joshua
struct wrapper_base {}; template<class T> struct wrapper : public wrapper_base {T val;} typedef wrapper* like_void_ptr;adalah void - * - minimal simulator yang menggunakan template.
pengguna253751
3
@ Joshua: Anda akan membutuhkan kutipan untuk "paling".
pengguna541686
@Mehrdad: Lihat cache L1.
Joshua
10

Masukan: Jika kita ingin mengizinkan beberapa jenis masukan, kita dapat membebani fungsi dan metode

Benar.

alternatifnya kita bisa mendefinisikan kelas basis umum.

Ini sebagian benar: bagaimana jika Anda tidak bisa mendefinisikan kelas basis umum, antarmuka atau serupa? Untuk menentukan mereka, Anda harus memiliki akses ke kode sumber, yang seringkali tidak memungkinkan.

Anda tidak menyebutkan template. Namun, template tidak dapat membantu Anda dengan polimorfisme: template tersebut bekerja dengan tipe statis yaitu yang dikenal pada waktu kompilasi.

void*dapat dianggap sebagai penyebut umum terendah. Di C ++, Anda biasanya tidak membutuhkannya karena (i) Anda tidak dapat melakukan banyak hal dengannya dan (ii) hampir selalu ada solusi yang lebih baik.

Lebih jauh lagi, Anda biasanya akan mengubahnya menjadi jenis beton lainnya. Itulah mengapa char *biasanya lebih baik, meskipun ini mungkin menunjukkan bahwa Anda mengharapkan string gaya-C, daripada blok data murni. Itulah mengapa void*lebih baik dari char*itu, karena memungkinkan transmisi implisit dari tipe penunjuk lainnya.

Anda seharusnya menerima beberapa data, bekerja dengannya dan menghasilkan keluaran; untuk mencapainya, Anda perlu mengetahui data yang sedang Anda kerjakan, jika tidak, Anda memiliki masalah lain yang bukan merupakan masalah yang awalnya Anda selesaikan. Banyak bahasa tidak memiliki void*dan tidak bermasalah dengan itu, misalnya.

Penggunaan sah lainnya

Saat mencetak alamat penunjuk dengan fungsi seperti printfpenunjuk harus memiliki void*tipe dan, oleh karena itu, Anda mungkin memerlukan cast ke void*

edmz
sumber
7

Ya, itu sama berguna seperti hal lain dalam bahasa ini.
Sebagai contoh, Anda dapat menggunakannya untuk menghapus jenis kelas yang dapat Anda transmisikan secara statis ke jenis yang tepat saat diperlukan, untuk memiliki antarmuka yang minimal dan fleksibel.

Dalam bahwa respon ada contoh penggunaan yang seharusnya memberi Anda ide.
Saya salin dan tempel di bawah ini demi kejelasan:

class Dispatcher {
    Dispatcher() { }

    template<class C, void(C::*M)() = C::receive>
    static void invoke(void *instance) {
        (static_cast<C*>(instance)->*M)();
    }

public:
    template<class C, void(C::*M)() = &C::receive>
    static Dispatcher create(C *instance) {
        Dispatcher d;
        d.fn = &invoke<C, M>;
        d.instance = instance;
        return d;
    }

    void operator()() {
        (fn)(instance);
    }

private:
    using Fn = void(*)(void *);
    Fn fn;
    void *instance;
};

Jelas, ini hanya salah satu dari sekumpulan kegunaan void*.

skypjack
sumber
4

Berinteraksi dengan fungsi perpustakaan eksternal yang mengembalikan sebuah pointer. Ini satu untuk aplikasi Ada.

extern "C" { void* ada_function();}

void* m_status_ptr = ada_function();

Ini mengembalikan pointer ke apapun yang Ada ingin beritahukan kepada Anda. Anda tidak perlu melakukan sesuatu yang mewah, Anda dapat memberikannya kembali kepada Ada untuk melakukan hal berikutnya. Faktanya, menguraikan pointer Ada di C ++ bukanlah hal yang sepele.

RedSonja
sumber
Tidak bisakah kita menggunakan autosebagai gantinya dalam kasus ini? Dengan asumsi bahwa tipe tersebut diketahui pada waktu kompilasi.
magu_
Ah, sekarang mungkin kita bisa. Kode tersebut berasal dari beberapa tahun yang lalu ketika saya membuat beberapa pembungkus Ada.
RedSonja
Ada tipe yang bisa jadi jahat - mereka membuatnya sendiri, Anda tahu, dan mengambil kesenangan yang tidak wajar dalam membuatnya menjadi rumit. Saya tidak diizinkan untuk mengubah antarmuka, itu akan terlalu mudah, dan itu mengembalikan beberapa hal buruk yang tersembunyi di petunjuk kekosongan itu. Ugh.
RedSonja
2

Singkatnya, C ++ sebagai bahasa yang ketat (tidak memperhitungkan peninggalan C seperti malloc () ) membutuhkan void * karena tidak memiliki induk umum dari semua jenis yang mungkin. Berbeda dengan ObjC, misalnya yang memiliki objek .

nredko
sumber
mallocdan newkeduanya kembali void *, jadi Anda akan memerlukannya jika meskipun ada kelas objek di C ++
Dmitry Grigoryev
malloc adalah peninggalan, tetapi dalam bahasa yang ketat baru harus mengembalikan objek *
nredko
bagaimana Anda mengalokasikan array bilangan bulat?
Dmitry Grigoryev
@DmitryGrigoryev operator new()kembali void *, tetapi newekspresi tidak
MM
1. Saya tidak yakin saya ingin melihat objek kelas basis virtual di atas setiap kelas dan jenis , termasuk int2. jika itu adalah EBC non-virtual, lalu apa bedanya dengan void*?
lorro
1

Hal pertama yang muncul di benak saya (yang saya curigai adalah kasus konkret dari beberapa jawaban di atas) adalah kemampuan untuk meneruskan instance objek ke threadproc di Windows.

Saya punya beberapa kelas C ++ yang perlu melakukan ini, mereka memiliki implementasi utas pekerja dan parameter LPVOID di API CreateThread () mendapatkan alamat implementasi metode statis di kelas sehingga utas pekerja dapat melakukan pekerjaan dengan contoh spesifik kelas. Transmisi statis sederhana di threadproc menghasilkan instance untuk digunakan, yang memungkinkan setiap objek yang dibuat memiliki thread pekerja dari implementasi metode statis tunggal.

AndyW
sumber
0

Dalam kasus multiple inheritance, jika Anda perlu mendapatkan pointer ke byte pertama dari potongan memori yang ditempati oleh suatu objek, Anda dapat dynamic_castmelakukannya void*.

Ancaman kecil
sumber