มีการใช้โมฆะ * อย่างถูกกฎหมายหรือไม่?


87

มีการใช้งานvoid*C ++ อย่างถูกกฎหมายหรือไม่? หรือสิ่งนี้ถูกแนะนำเพราะมี C?

เพื่อสรุปความคิดของฉัน:

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

ผลลัพธ์ : ฉันไม่สามารถนึกถึงสถานการณ์ใด ๆ ที่ฉันต้องการได้รับ void*เมื่อเทียบกับสิ่งที่มาจากคลาสพื้นฐานที่รู้จัก

เพียงเพื่อให้ชัดเจนว่าฉันหมายถึงอะไร: ฉันไม่ได้ถามเป็นพิเศษว่ามี use-case สำหรับvoid*แต่ถ้ามีvoid*ทางเลือกที่ดีที่สุดหรือมีให้เลือกเท่านั้น ซึ่งได้รับคำตอบอย่างสมบูรณ์แบบจากหลาย ๆ คนด้านล่าง


3
แล้วเวลาที่คุณต้องการมีหลายประเภทเช่น int & std :: string ล่ะ?
Amir

12
@Amir, variant, anyยูเนี่ยนแท็ก อะไรก็ได้ที่สามารถบอกประเภทของเนื้อหาจริงและปลอดภัยกว่าในการใช้
Revolver_Ocelot

15
"C has it" เป็นเหตุผลที่หนักแน่นเพียงพอไม่ต้องมองหาอะไรเพิ่มเติม การหลีกเลี่ยงให้มากที่สุดเป็นสิ่งที่ดีในภาษาใดภาษาหนึ่ง
. 'สรรพนาม' ม.

1
มีสิ่งหนึ่ง: การทำงานร่วมกับ API สไตล์ C เป็นเรื่องที่น่าอึดอัดหากไม่มีมัน
Martin James

1
การใช้งานที่น่าสนใจคือการลบประเภทสำหรับเวกเตอร์ของพอยน์เตอร์
Paolo M

คำตอบ:


81

void*เป็นอย่างน้อยที่จำเป็นเนื่องจาก::operator new(เช่นทุกoperator new... ) และจากmallocและเป็นอาร์กิวเมนต์ของตัวnewดำเนินการตำแหน่ง

void*ถือได้ว่าเป็นซุปเปอร์ไทป์ทั่วไปของตัวชี้ทุกประเภท ดังนั้นจึงไม่ได้หมายถึงตัวชี้ไปที่voidแต่ชี้ไปที่อะไรก็ได้

BTW หากคุณต้องการเก็บข้อมูลบางส่วนสำหรับตัวแปรส่วนกลางที่ไม่เกี่ยวข้องหลายตัวคุณอาจใช้บางส่วนstd::map<void*,int> score; หลังจากที่ได้ประกาศ global int x;and double y;and std::string s;do score[&x]=1;and score[&y]=2;และscore[&z]=3;

memset ต้องการที่void*อยู่ (ที่อยู่ทั่วไปมากที่สุด)

นอกจากนี้ระบบ POSIX ยังมีdlsymและประเภทผลตอบแทนที่ชัดเจนควรเป็นvoid*


1
นั่นเป็นจุดที่ดีมาก ฉันลืมที่จะพูดถึงว่าฉันคิดถึงผู้ใช้ภาษามากกว่าด้านการนำไปใช้งาน อีกสิ่งหนึ่งที่ฉันไม่เข้าใจ: เป็นฟังก์ชันใหม่หรือไม่? ฉันคิดว่ามันเป็นคีย์เวิร์ดมากกว่า
magu_

9
new เป็นตัวดำเนินการเช่น + และ sizeof
Joshua

7
แน่นอนว่าสามารถคืนความทรงจำได้char *ด้วย พวกเขามีความใกล้ชิดกับด้านนี้มากแม้ว่าความหมายจะแตกต่างกันเล็กน้อย
edmz

@ โจชัว. ไม่ทราบว่า. ขอบคุณที่สอนฉันเรื่องนี้ ตั้งแต่C++ถูกเขียนในนี่คือเหตุผลพอที่จะมีC++ void*ต้องรักไซต์นี้ ถามคำถามเดียวเรียนรู้มาก
magu_

3
@black ย้อนกลับไปในวันเก่า, C ไม่ได้มีvoid*ประเภท ฟังก์ชั่นห้องสมุดมาตรฐานทั้งหมดที่ใช้char*
Cole Johnson

28

มีหลายเหตุผลที่จะใช้void*3 สิ่งที่พบบ่อยที่สุด:

  1. โต้ตอบกับไลบรารี C โดยใช้void*ในอินเทอร์เฟซ
  2. พิมพ์ลบ
  3. แสดงถึงหน่วยความจำที่ไม่ได้พิมพ์

ในลำดับย้อนกลับการแสดงหน่วยความจำที่ไม่ได้พิมพ์ด้วยvoid*(3) แทนที่จะเป็นchar*(หรือตัวแปร) จะช่วยป้องกันการคำนวณทางคณิตศาสตร์ของตัวชี้โดยบังเอิญ มีการใช้งานน้อยมากvoid*ดังนั้นจึงต้องมีการหล่อก่อนจึงจะเป็นประโยชน์ และแน่นอนชอบมากกับchar*ไม่มีปัญหากับนามแฝง

Type-erasure (2) ยังคงใช้ใน C ++ ร่วมกับเทมเพลตหรือไม่:

  • รหัสที่ไม่ใช่ทั่วไปช่วยลดการขยายตัวของไบนารีซึ่งมีประโยชน์ในเส้นทางเย็นแม้ในรหัสทั่วไป
  • รหัสที่ไม่ใช่ทั่วไปเป็นสิ่งที่จำเป็นสำหรับการจัดเก็บในบางครั้งแม้ในคอนเทนเนอร์ทั่วไปเช่น std::function

และเห็นได้ชัดว่าเมื่ออินเทอร์เฟซที่คุณจัดการกับการใช้งานvoid*(1) คุณมีทางเลือกน้อย


15

โอ้ใช่. แม้ใน C ++ บางครั้งเราก็void *ใช้มากกว่าtemplate<class T*>เพราะบางครั้งโค้ดพิเศษจากการขยายเทมเพลตมีน้ำหนักมากเกินไป

โดยทั่วไปฉันจะใช้มันเป็นการนำไปใช้จริงของประเภทและประเภทแม่แบบจะสืบทอดมาจากนั้นและห่อหุ้ม

นอกจากนี้ allocators แผ่นที่กำหนดเอง (ผู้ประกอบการใช้งานใหม่) void *ต้องใช้ นี่เป็นหนึ่งในเหตุผลที่ g ++ เพิ่มส่วนขยายของการอนุญาตตัวชี้ทางคณิตศาสตร์void *ราวกับว่ามีขนาด 1


ขอบคุณสำหรับคำตอบ. คุณพูดถูกฉันลืมพูดถึงเทมเพลต แต่เทมเพลตจะไม่เหมาะกับงานนี้ไปกว่านี้อีกแล้วเนื่องจากไม่ค่อยมีการแนะนำการลงโทษด้านประสิทธิภาพหรือไม่ ( stackoverflow.com/questions/2442358/… )
magu_

1
เทมเพลตแนะนำการปรับขนาดโค้ดซึ่งส่วนใหญ่เป็นโทษด้านประสิทธิภาพเช่นกัน
Joshua

struct wrapper_base {}; template<class T> struct wrapper : public wrapper_base {T val;} typedef wrapper* like_void_ptr;เป็นโมฆะน้อยที่สุด - * - โปรแกรมจำลองโดยใช้เทมเพลต
user253751

3
@ โจชัว: คุณจะต้องมีการอ้างอิงสำหรับ "ส่วนใหญ่"
541686

@ Mehrdad: ดูแคช L1
Joshua

10

อินพุต: หากเราต้องการอนุญาตให้ป้อนข้อมูลหลายประเภทเราสามารถโอเวอร์โหลดฟังก์ชันและวิธีการได้

จริง.

หรือเราสามารถกำหนดคลาสพื้นฐานทั่วไป

นี่เป็นความจริงบางส่วน: จะเกิดอะไรขึ้นถ้าคุณไม่สามารถกำหนดคลาสพื้นฐานทั่วไปอินเทอร์เฟซหรือสิ่งที่คล้ายกัน ในการกำหนดสิ่งที่คุณต้องมีเพื่อเข้าถึงซอร์สโค้ดซึ่งมักไม่สามารถทำได้

คุณไม่ได้กล่าวถึงเทมเพลต อย่างไรก็ตามเทมเพลตไม่สามารถช่วยคุณเกี่ยวกับความหลากหลายได้: มันทำงานกับประเภทคงที่เช่นที่รู้จักกันในเวลาคอมไพล์

void*อาจถือว่าเป็นตัวหารร่วมที่ต่ำที่สุด ใน C ++ โดยทั่วไปคุณไม่จำเป็นต้องใช้เพราะ (i) คุณไม่สามารถทำอะไรได้มากนักและ (ii) มีวิธีแก้ปัญหาที่ดีกว่าเกือบตลอดเวลา

ยิ่งไปกว่านั้นคุณมักจะเปลี่ยนเป็นคอนกรีตประเภทอื่น ๆ นั่นเป็นเหตุผลว่าทำไมจึงchar *ดีกว่าแม้ว่าอาจบ่งบอกว่าคุณคาดหวังสตริงสไตล์ C แทนที่จะเป็นบล็อกข้อมูลทั้งหมด นั่นเป็นเหตุผลที่void*ดีกว่าchar*สำหรับสิ่งนั้นเนื่องจากอนุญาตให้ส่งโดยนัยจากตัวชี้ประเภทอื่น ๆ

คุณควรจะได้รับข้อมูลบางอย่างทำงานกับมันและสร้างผลลัพธ์ เพื่อให้บรรลุเป้าหมายนี้คุณจำเป็นต้องทราบข้อมูลที่คุณกำลังทำงานด้วยมิฉะนั้นคุณจะมีปัญหาอื่นซึ่งไม่ใช่ปัญหาที่คุณกำลังแก้อยู่ในตอนแรก หลายภาษาไม่มีvoid*และไม่มีปัญหาเช่นนั้น

การใช้งานที่ถูกต้องตามกฎหมายอีก

เมื่อพิมพ์ที่อยู่ตัวชี้ด้วยฟังก์ชันเช่นprintfตัวชี้จะต้องมีvoid*ประเภทดังนั้นคุณอาจต้องแคสต์เพื่อvoid*


7

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

ในที่การตอบสนองมีตัวอย่างของการใช้ที่ควรให้ความคิดเป็นนักการ
ฉันคัดลอกและวางไว้ด้านล่างเพื่อความชัดเจน:

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

void*เห็นได้ชัดว่านี้เป็นเพียงหนึ่งในพวงของการใช้ประโยชน์จาก


4

การเชื่อมต่อกับฟังก์ชันไลบรารีภายนอกซึ่งส่งกลับตัวชี้ นี่คือหนึ่งสำหรับแอปพลิเคชัน Ada

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

void* m_status_ptr = ada_function();

สิ่งนี้ส่งกลับตัวชี้ไปยังสิ่งที่ Ada ต้องการบอกคุณ คุณไม่จำเป็นต้องทำอะไรแฟนซีกับมันคุณสามารถส่งคืนให้ Ada เพื่อทำสิ่งต่อไปได้ ในความเป็นจริงการแยกตัวชี้ Ada ใน C ++ นั้นไม่สำคัญ


เราไม่สามารถใช้autoแทนในกรณีนี้ได้หรือไม่? สมมติว่ารู้จักประเภทในเวลาคอมไพล์
magu_

อาวันนี้เราคงทำได้ รหัสนั้นมาจากเมื่อหลายปีก่อนเมื่อฉันทำกระดาษห่อ Ada
RedSonja

ประเภท Ada อาจเป็นปีศาจ - พวกเขาสร้างขึ้นเองคุณรู้และมีความสุขในการทำให้มันยุ่งยาก ฉันไม่ได้รับอนุญาตให้เปลี่ยนอินเทอร์เฟซนั่นคงง่ายเกินไปและมันส่งคืนสิ่งที่น่ารังเกียจบางอย่างที่ซ่อนอยู่ในตัวชี้ที่ว่างเปล่าเหล่านั้น ฮึ.
RedSonja

2

ในระยะสั้น C ++ เป็นภาษาที่เข้มงวด (ไม่คำนึงถึง C พระธาตุเช่นmalloc () ) ต้องการโมฆะ * เนื่องจากไม่มีผู้ปกครองร่วมกันทุกประเภทที่เป็นไปได้ ซึ่งแตกต่างจาก ObjC เช่นซึ่งมีวัตถุ


mallocและnewทั้งสองกลับมาvoid *ดังนั้นคุณจะต้องแม้ว่าจะมีคลาสอ็อบเจ็กต์ใน C ++
Dmitry Grigoryev

malloc เป็น relict แต่ในภาษาที่เข้มงวดnewควรส่งคืน object *
nredko

คุณจะจัดสรรอาร์เรย์ของจำนวนเต็มได้อย่างไร?
Dmitry Grigoryev

@DmitryGrigoryev operator new()ส่งคืนvoid *แต่newนิพจน์ไม่ได้
MM

1. ฉันไม่แน่ใจว่าฉันต้องการเห็นออบเจ็กต์คลาสฐานเสมือนเหนือทุกคลาสและทุกประเภทรวมถึงint2. ถ้าไม่ใช่ EBC เสมือนจริงมันแตกต่างจากvoid*อย่างไร?
lorro

1

สิ่งแรกที่เกิดขึ้นในใจของฉัน (ซึ่งฉันสงสัยว่าเป็นกรณีที่เป็นรูปธรรมของสองคำตอบข้างต้น) คือความสามารถในการส่งผ่านอินสแตนซ์วัตถุไปยัง threadproc ใน Windows

ฉันมีคลาส C ++ สองสามคลาสที่ต้องทำสิ่งนี้พวกเขามีการใช้เธรดของผู้ปฏิบัติงานและพารามิเตอร์ LPVOID ใน CreateThread () API ได้รับที่อยู่ของการใช้วิธีการแบบคงที่ในคลาสเพื่อให้เธรดผู้ปฏิบัติงานสามารถทำงานได้ อินสแตนซ์เฉพาะของคลาส การส่งกลับแบบคงที่อย่างง่ายใน threadproc ทำให้อินสแตนซ์สามารถทำงานได้โดยอนุญาตให้อ็อบเจ็กต์อินสแตนซ์แต่ละตัวมีเธรดผู้ปฏิบัติงานจากการใช้งานวิธีการแบบคงที่เดียว


0

ในกรณีของมรดกหลายถ้าคุณต้องการที่จะได้รับตัวชี้ไปยังไบต์แรกของก้อนหน่วยความจำที่ถูกครอบครองโดยวัตถุที่คุณอาจจะdynamic_castvoid*

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