เหตุใด shared_ptr <void> ถูกกฎหมายในขณะที่ unique_ptr <void> มีรูปแบบไม่ถูกต้อง


103

คำถามนี้เหมาะกับชื่อจริงๆ: ฉันอยากรู้ว่าอะไรคือเหตุผลทางเทคนิคสำหรับความแตกต่างนี้ แต่ยังมีเหตุผลด้วย?

std::shared_ptr<void> sharedToVoid; // legal;
std::unique_ptr<void> uniqueToVoid; // ill-formed;

คำตอบ:


123

เป็นเพราะstd::shared_ptrใช้การลบประเภทในขณะที่std::unique_ptrไม่


เนื่องจากstd::shared_ptrใช้การลบประเภทจึงสนับสนุนคุณสมบัติที่น่าสนใจอีกอย่างหนึ่งได้แก่ มันไม่ได้จำเป็นต้องมีประเภทของ Deleter ที่เป็นประเภทแม่แบบการโต้เถียงกับแม่แบบชั้นเรียน ดูคำประกาศของพวกเขา:

template<class T,class Deleter = std::default_delete<T> > 
class unique_ptr;

ซึ่งมีDeleterเป็นพารามิเตอร์ type ในขณะที่

template<class T> 
class shared_ptr;

ไม่มีเลย

คำถามคือเหตุใดจึงshared_ptrใช้ type-erasure มันเป็นเช่นนั้นเพราะต้องรองรับการนับการอ้างอิงและเพื่อรองรับสิ่งนี้จึงต้องจัดสรรหน่วยความจำจากฮีปและเนื่องจากต้องจัดสรรหน่วยความจำต่อไปจึงไปอีกขั้นหนึ่งและใช้การลบประเภท - ซึ่งต้องการฮีป จัดสรรด้วย. โดยพื้นฐานแล้วมันเป็นแค่การฉวยโอกาส!

เนื่องจากการลบประเภทstd::shared_ptrสามารถรองรับสองสิ่ง:

  • สามารถเก็บวัตถุชนิดใด ๆ ที่เป็นvoid*, แต่มันก็ยังคงสามารถลบวัตถุในการทำลายอย่างถูกต้องโดยถูกต้องอัญเชิญ destructor
  • ประเภทของ Deleter ไม่ผ่านเป็นชนิดอาร์กิวเมนต์แม่แบบชั้นเรียนซึ่งหมายถึงเสรีภาพนิด ๆ หน่อย ๆโดยไม่สูญเสียความปลอดภัยประเภท

ได้เลย นั่นคือทั้งหมดที่เกี่ยวกับวิธีการstd::shared_ptrทำงาน

ตอนนี้คำถามคือสามารถstd::unique_ptrจัดเก็บวัตถุเป็น void* ? คำตอบคือใช่หากคุณส่ง deleter ที่เหมาะสมเป็นอาร์กิวเมนต์ นี่คือการสาธิตดังกล่าว:

int main()
{
    auto deleter = [](void const * data ) {
        int const * p = static_cast<int const*>(data);
        std::cout << *p << " located at " << p <<  " is being deleted";
        delete p;
    };

    std::unique_ptr<void, decltype(deleter)> p(new int(959), deleter);

} //p will be deleted here, both p ;-)

เอาท์พุท ( การสาธิตออนไลน์ ):

959 located at 0x18aec20 is being deleted

คุณถามคำถามที่น่าสนใจมากในความคิดเห็น:

ในกรณีของฉันฉันจะต้องใช้ตัวลบการลบประเภท แต่ดูเหมือนว่าจะเป็นไปได้เช่นกัน (โดยมีค่าใช้จ่ายในการจัดสรรฮีปบางส่วน) โดยทั่วไปหมายความว่ามีจุดเฉพาะสำหรับสมาร์ทพอยน์เตอร์ประเภทที่ 3 ซึ่งเป็นสมาร์ทพอยน์เตอร์ที่เป็นเจ้าของเฉพาะตัวพร้อมการลบประเภท

ซึ่ง@Steve Jessopแนะนำวิธีแก้ปัญหาต่อไปนี้

ฉันไม่เคยลองสิ่งนี้จริง ๆ แต่บางทีคุณอาจทำได้โดยใช้ตัวเลือกที่เหมาะสมstd::functionกับประเภท deleter ด้วยunique_ptr? สมมติว่าใช้งานได้จริงแสดงว่าคุณทำเสร็จแล้วความเป็นเจ้าของ แต่เพียงผู้เดียวและตัวลบประเภทที่ถูกลบ

ทำตามคำแนะนำนี้ฉันใช้สิ่งนี้ (แม้ว่าจะไม่ได้ใช้ประโยชน์std::functionเนื่องจากดูเหมือนว่าไม่จำเป็น):

using unique_void_ptr = std::unique_ptr<void, void(*)(void const*)>;

template<typename T>
auto unique_void(T * ptr) -> unique_void_ptr
{
    return unique_void_ptr(ptr, [](void const * data) {
         T const * p = static_cast<T const*>(data);
         std::cout << "{" << *p << "} located at [" << p <<  "] is being deleted.\n";
         delete p;
    });
}

int main()
{
    auto p1 = unique_void(new int(959));
    auto p2 = unique_void(new double(595.5));
    auto p3 = unique_void(new std::string("Hello World"));
}  

เอาท์พุท ( การสาธิตออนไลน์ ):

{Hello World} located at [0x2364c60] is being deleted.
{595.5} located at [0x2364c40] is being deleted.
{959} located at [0x2364c20] is being deleted.

หวังว่าจะช่วยได้


13
ตอบได้ดี +1. แต่คุณอาจทำให้ดียิ่งขึ้นโดยการระบุอย่างชัดเจนว่า a std::unique_ptr<void, D>ยังคงเป็นไปได้โดยการDระบุ
Angew ไม่ภูมิใจใน SO

1
@Angrew: เยี่ยมมากคุณพบคำถามที่แท้จริงที่ไม่ได้เขียนไว้ในคำถามของฉัน;)
Ad N

@Nawaz: ขอบคุณครับ ในกรณีของฉันฉันจะต้องใช้ตัวลบการลบประเภท แต่ดูเหมือนว่าจะเป็นไปได้เช่นกัน (โดยมีค่าใช้จ่ายในการจัดสรรฮีปบางส่วน) โดยทั่วไปหมายความว่ามีจุดเฉพาะสำหรับตัวชี้อัจฉริยะประเภทที่ 3: ตัวชี้อัจฉริยะที่เป็นเจ้าของเฉพาะพร้อมการลบประเภทหรือไม่?
แอด N

8
@AdN: ฉันไม่เคยลองสิ่งนี้จริง ๆ แต่บางทีคุณอาจทำได้โดยใช้ความเหมาะสมstd::functionเป็นประเภท deleter ด้วยunique_ptr? สมมติว่าใช้งานได้จริงแสดงว่าคุณทำเสร็จแล้วความเป็นเจ้าของ แต่เพียงผู้เดียวและตัวลบประเภทที่ถูกลบ
Steve Jessop

ไวยากรณ์: "ทำไมต้องเป็นคำกริยา X Y" ควรจะเป็น "ทำไมไม่ X กริยา Y?" เป็นภาษาอังกฤษ.
zwol

7

หนึ่งในเหตุผลคือหนึ่งในกรณีการใช้งานจำนวนมากของ a shared_ptr- คือตัวบ่งชี้อายุการใช้งานหรือแมวมอง

สิ่งนี้ถูกกล่าวถึงในเอกสารการเพิ่มต้นฉบับ:

auto register_callback(std::function<void()> closure, std::shared_ptr<void> pv)
{
    auto closure_target = { closure, std::weak_ptr<void>(pv) };
    ...
    // store the target somewhere, and later....
}

void call_closure(closure_target target)
{
    // test whether target of the closure still exists
    auto lock = target.sentinel.lock();
    if (lock) {
        // if so, call the closure
        target.closure();
    }
}

closure_targetสิ่งนี้อยู่ที่ไหน:

struct closure_target {
    std::function<void()> closure;
    std::weak_ptr<void> sentinel;
};

ผู้โทรจะลงทะเบียนการโทรกลับในลักษณะนี้:

struct active_object : std::enable_shared_from_this<active_object>
{
    void start() {
      event_emitter_.register_callback([this] { this->on_callback(); }, 
                                       shared_from_this());
    }

    void on_callback()
    {
        // this is only ever called if we still exist 
    }
};

เนื่องจากshared_ptr<X>สามารถเปลี่ยนแปลงได้ตลอดเวลาshared_ptr<void>event_emitter จึงสามารถไม่รู้ชนิดของวัตถุที่เรียกกลับเข้ามาได้อย่างมีความสุข

ข้อตกลงนี้จะปล่อยสมาชิกไปยังผู้ปล่อยเหตุการณ์ของภาระหน้าที่ในการจัดการข้ามกรณี (จะเกิดอะไรขึ้นหากการเรียกกลับในคิวรอให้ดำเนินการในขณะที่ active_object หายไป) และยังหมายความว่าไม่จำเป็นต้องซิงโครไนซ์การยกเลิกการสมัคร weak_ptr<void>::lockเป็นการดำเนินการที่ซิงโครไนซ์

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