deleter ของ shared_ptr ถูกเก็บไว้ในหน่วยความจำที่จัดสรรโดยตัวจัดสรรที่กำหนดเองหรือไม่


22

บอกว่าผมมีshared_ptrกับการจัดสรรที่กำหนดเองและ Deleter ที่กำหนดเอง

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

สิ่งนี้ไม่ได้ระบุหรือฉันเพิ่งจะพลาดบางสิ่ง

คำตอบ:


11

util.smartptr.shared.const / 9 ใน C ++ 11:

ผลกระทบ: โครงสร้างวัตถุ shared_ptr ที่เป็นเจ้าของวัตถุ p และ deleter d Constructor ที่สองและสี่จะใช้สำเนาของ a เพื่อจัดสรรหน่วยความจำสำหรับใช้ภายใน

ตัวสร้างที่สองและสี่มีต้นแบบเหล่านี้:

template<class Y, class D, class A> shared_ptr(Y* p, D d, A a);
template<class D, class A> shared_ptr(nullptr_t p, D d, A a);

ในฉบับร่างล่าสุด util.smartptr.shared.const / 10 นั้นเทียบเท่ากับจุดประสงค์ของเรา:

ผลกระทบ: โครงสร้างวัตถุ shared_ptr ที่เป็นเจ้าของวัตถุ p และ deleter d เมื่อ T ไม่ใช่ประเภทอาเรย์คอนสตรัคเตอร์ตัวแรกและตัวที่สองจะเปิดใช้งาน shared_from_this ด้วย p Constructor ที่สองและสี่จะใช้สำเนาของ a เพื่อจัดสรรหน่วยความจำสำหรับใช้ภายใน หากมีข้อผิดพลาดเกิดขึ้นจะเรียกว่า d (p)

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

  • แม้ว่าอินเทอร์เฟซของshared_ptrอนุญาตการใช้งานที่ไม่เคยมีบล็อกควบคุมและทั้งหมดshared_ptrและweak_ptrใส่ไว้ในรายการที่เชื่อมโยง แต่ไม่มีการนำไปใช้ในทางปฏิบัติ นอกจากนี้การใช้ถ้อยคำได้รับการแก้ไขเช่นสมมติว่าuse_countมีการแบ่งปัน

  • เดลเตอร์จำเป็นต้องเคลื่อนย้ายที่สามารถสร้างได้เท่านั้น shared_ptrดังนั้นจึงเป็นไปไม่ได้ที่จะมีหลายชุดใน

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

DR ที่เกี่ยวข้องที่ฉันพบ: 545 , 575 , 2434 (ซึ่งยอมรับว่าการใช้งานทั้งหมดกำลังใช้บล็อกควบคุมและดูเหมือนจะบ่งบอกว่าข้อ จำกัด หลายเธรดค่อนข้างได้รับคำสั่ง), 2802 (ซึ่งต้องการให้ deleter ย้ายเท่านั้นที่สามารถสร้างได้ deleter จะถูกคัดลอกระหว่างหลายshared_ptrรายการ


2
"การจัดสรรหน่วยความจำสำหรับใช้ภายใน" จะเกิดอะไรขึ้นถ้าการนำไปปฏิบัติไม่ได้จะจัดสรรหน่วยความจำสำหรับใช้ภายในเพื่อเริ่มต้นด้วย? มันสามารถใช้สมาชิก
LF

1
@LF ไม่สามารถอินเทอร์เฟซไม่อนุญาต
AProgrammer

ในทางทฤษฎีแล้วมันยังสามารถใช้ "การเพิ่มประสิทธิภาพ deleter ขนาดเล็ก" ได้ไหม?
LF

สิ่งที่แปลกคือฉันไม่พบสิ่งใดเกี่ยวกับการใช้ตัวจัดสรร (สำเนาa) ตัวเดียวกันเพื่อยกเลิกการจัดสรรหน่วยความจำนั้น aซึ่งจะบ่งบอกถึงการจัดเก็บสำเนาของว่าบางส่วน ไม่มีข้อมูลเกี่ยวกับเรื่องนี้ใน [util.smartptr.shared.dest]
Daniel Langr

1
@DanielsaysreinstateMonica ฉันสงสัยว่าใน util.smartptr.shared / 1: "แม่แบบคลาส shared_ptr จะเก็บตัวชี้โดยปกติจะได้รับผ่านทางใหม่ shared_ptr ใช้ความหมายของการเป็นเจ้าของร่วมกันสุดท้ายเจ้าของตัวชี้ที่เหลือมีหน้าที่รับผิดชอบในการทำลายวัตถุ หรือปล่อยทรัพยากรที่เกี่ยวข้องกับตัวชี้ที่เก็บไว้ " การปล่อยทรัพยากรที่เกี่ยวข้องกับตัวชี้ที่เก็บไว้ไม่ได้มีไว้สำหรับสิ่งนั้น แต่บล็อกควบคุมควรอยู่รอดเช่นกันจนกว่าตัวชี้จุดอ่อนสุดท้ายจะถูกลบ
AProgrammer

4

จากstd :: shared_ptrเรามี:

บล็อกควบคุมเป็นวัตถุที่จัดสรรแบบไดนามิกที่เก็บ:

  • ตัวชี้ไปยังวัตถุที่ได้รับการจัดการหรือวัตถุที่มีการจัดการเอง
  • deleter (ประเภทลบ);
  • ตัวจัดสรร (ลบประเภท);
  • จำนวนของ shared_ptrs ที่เป็นเจ้าของวัตถุที่ถูกจัดการ
  • จำนวนของ weak_ptrs ที่อ้างถึงอ็อบเจ็กต์ที่ถูกจัดการ

และจากstd :: allocate_sharedเราได้รับ:

template< class T, class Alloc, class... Args >
shared_ptr<T> allocate_shared( const Alloc& alloc, Args&&... args );

โครงสร้างวัตถุประเภท T และ wraps ใน std :: shared_ptr [... ] เพื่อใช้การจัดสรรหนึ่งครั้งสำหรับทั้งบล็อกควบคุมของตัวชี้ที่ใช้ร่วมกันและวัตถุ T

ดังนั้นดูเหมือนว่ามาตรฐาน :: allocate_sharedควรจัดสรรกับdeleterAlloc

แก้ไข: และจากn4810§ 20.11.3.6 การสร้าง [util.smartptr.shared.create]

1 ข้อกำหนดทั่วไปที่ใช้กับทุกmake_shared, allocate_shared, make_shared_default_initและallocate_shared_default_initoverloads เว้นแต่จะระบุไว้ด้านล่าง

[ ... ]

7 ข้อสังเกต: (7.1) - การใช้งานควรดำเนินการจัดสรรหน่วยความจำไม่เกินหนึ่งครั้ง [หมายเหตุ: สิ่งนี้ให้ประสิทธิภาพเทียบเท่าตัวชี้สมาร์ทที่ล่วงล้ำ - บันทึกย่อ]

[เน้นที่เหมืองทั้งหมด]

ดังนั้นมาตรฐานจะบอกว่าstd::allocate_shared ควรใช้Allocสำหรับบล็อกควบคุม


1
ฉันขอโทษด้วย cppreference ไม่ใช่ข้อความเชิงบรรทัดฐาน มันเป็นแหล่งข้อมูลที่ดี แต่ไม่จำเป็นสำหรับคำถามนักกฎหมายภาษา
StoryTeller - Unslander Monica

@ StoryTeller-UnslanderMonica เห็นด้วยอย่างเต็มที่ - มองผ่านมาตรฐานล่าสุดและไม่พบสิ่งใดเลยที่เกิดขึ้นพร้อมกับ cppreference
Paul Evans


พบn4810และปรับปรุงคำตอบ
พอลอีแวนส์

1
อย่างไรก็ตามสิ่งนี้กำลังพูดถึงmake_sharedไม่ใช่ตัวสร้าง ถึงกระนั้นฉันสามารถใช้สมาชิกสำหรับนักการตลาดขนาดเล็ก
LF

3

ฉันเชื่อว่านี่ไม่ได้ระบุไว้

นี่คือข้อกำหนดของ Constructor ที่เกี่ยวข้อง: [util.smartptr.shared.const] / 10

template<class Y, class D> shared_ptr(Y* p, D d);
template<class Y, class D, class A> shared_ptr(Y* p, D d, A a);
template <class D> shared_ptr(nullptr_t p, D d);
template <class D, class A> shared_ptr(nullptr_t p, D d, A a);

ผลกระทบ:โครงสร้างshared_­ptrวัตถุที่เป็นเจ้าของวัตถุpและ dDeleter เมื่อTไม่ได้เป็นชนิดอาร์เรย์ก่อสร้างครั้งแรกและครั้งที่สองเปิดใช้งานด้วยshared_­from_­this pก่อสร้างที่สองและสี่จะต้องใช้สำเนาaจัดสรรหน่วยความจำสำหรับการใช้ภายใน หากมีการโยนข้อยกเว้นจะถูกd(p)เรียก

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

template <typename T>
class shared_ptr : /* ... */ {
    // ...
    std::aligned_storage<16> _Small_deleter;
    // ...
public:
    // ...
    template <class _D, class _A>
    shared_ptr(nullptr_t, _D __d, _A __a) // for example
        : _Allocator_base{__a}
    {
        if constexpr (sizeof(_D) <= 16)
            _Construct_at(&_Small_deleter, std::move(__d));
        else
            // use 'a' to allocate storage for the deleter
    }
// ...
};

การใช้งานนี้ "ใช้สำเนาของaการจัดสรรหน่วยความจำสำหรับการใช้ภายใน" หรือไม่? ใช่. aมันไม่เคยจัดสรรหน่วยความจำยกเว้นโดยใช้ มีปัญหามากมายเกี่ยวกับการใช้งานแบบไร้เดียงสานี้ แต่สมมุติว่ามันใช้การจัดสรรโดยใช้ทั้งหมด แต่กรณีที่ง่ายที่สุดที่shared_ptrสร้างขึ้นโดยตรงจากตัวชี้และไม่เคยคัดลอกหรือย้ายหรืออ้างอิงอย่างอื่นและไม่มีภาวะแทรกซ้อนอื่น ๆ ประเด็นก็คือเพียงเพราะเราล้มเหลวในการจินตนาการว่าการใช้งานที่ถูกต้องไม่ได้พิสูจน์ด้วยตนเองว่ามันไม่สามารถมีอยู่จริงในทางทฤษฎี ฉันไม่ได้พูดว่าการใช้งานดังกล่าวสามารถพบได้ในโลกแห่งความเป็นจริงเพียงว่ามาตรฐานดูเหมือนจะไม่ได้ห้ามอย่างจริงจัง


IMO ของคุณshared_ptrสำหรับประเภทขนาดเล็กจัดสรรหน่วยความจำบนสแต็ก และเพื่อให้ไม่ตรงตามข้อกำหนดมาตรฐาน
bartop

1
@bartop มันไม่“ จัดสรร” หน่วยความจำใด ๆ บนสแต็ก _Smaller_deleter เป็นส่วนหนึ่งของการเป็นตัวแทนของ shared_ptr โดยไม่มีเงื่อนไข การเรียกคอนสตรัคเตอร์ในพื้นที่นี้ไม่ได้หมายถึงการจัดสรรอะไรเลย มิฉะนั้นแม้การถือตัวชี้ไปยังบล็อกควบคุมนับเป็น "การจัดสรรหน่วยความจำ" ใช่ไหม? :-)
LF

แต่ไม่จำเป็นต้องลอกเลียนแบบ deleter ดังนั้นวิธีนี้จะทำงานอย่างไร
Nicol Bolas

@NicolBolas Umm ... ใช้std::move(__d)และถอยกลับไปallocateเมื่อต้องการสำเนา
LF
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.