เหตุใดจึงทำให้ unique_ptr <Derived> ส่งไปยัง unique_ptr <Base> โดยปริยาย


21

ฉันเขียนรหัสต่อไปนี้ซึ่งใช้ในunique_ptr<Derived>ที่ที่unique_ptr<Base>คาดไว้

class Base {
    int i;
 public:
    Base( int i ) : i(i) {}
    int getI() const { return i; }
};

class Derived : public Base {
    float f;
 public:
    Derived( int i, float f ) : Base(i), f(f) {}
    float getF() const { return f; }
};

void printBase( unique_ptr<Base> base )
{
    cout << "f: " << base->getI() << endl;
}

unique_ptr<Base> makeBase()
{
    return make_unique<Derived>( 2, 3.0f );
}

unique_ptr<Derived> makeDerived()
{
    return make_unique<Derived>( 2, 3.0f );
}

int main( int argc, char * argv [] )
{
    unique_ptr<Base> base1 = makeBase();
    unique_ptr<Base> base2 = makeDerived();
    printBase( make_unique<Derived>( 2, 3.0f ) );

    return 0;
}

และฉันคาดหวังว่ารหัสนี้จะไม่รวบรวมเพราะตามความเข้าใจของฉันunique_ptr<Base>และunique_ptr<Derived>เป็นประเภทที่ไม่เกี่ยวข้องและunique_ptr<Derived>ไม่ได้รับมาจากความจริงunique_ptr<Base>ดังนั้นการมอบหมายไม่ควรทำงาน

แต่ต้องขอบคุณเวทย์มนตร์ที่ใช้งานได้และฉันไม่เข้าใจว่าทำไมหรือถึงแม้ว่ามันจะปลอดภัยในการทำเช่นนั้น มีคนอธิบายได้ไหม


3
ตัวชี้สมาร์ทคือการเพิ่มสิ่งที่พอยน์เตอร์ไม่สามารถ จำกัด ได้ หากสิ่งนี้ไม่เป็นไปได้unique_ptrจะค่อนข้างไร้ประโยชน์เมื่อมีการสืบทอด
idclev 463035818

3
"แต่ด้วยความมหัศจรรย์บางคนก็ทำงาน" เกือบคุณจะได้รับ UB เนื่องจากBaseไม่มีตัวทำลายเสมือน
Jarod42

คำตอบ:


25

ความมหัศจรรย์ที่คุณกำลังมองหาคือคอนสตรัคเตอร์การแปลง # 6 ที่นี่ :

template<class U, class E>
unique_ptr(unique_ptr<U, E> &&u) noexcept;

มันช่วยให้การสร้างstd::unique_ptr<T>โดยนัยจากการหมดอายุstd::unique_ptr<U> ถ้า (การคัดสรรมากกว่า deleters เพื่อความชัดเจน):

unique_ptr<U, E>::pointer เปลี่ยนแปลงได้โดยปริยายเป็น pointer

ซึ่งก็คือมันเป็นการเลียนแบบการแปลงตัวชี้แบบดิบซึ่งรวมถึงการแปลงที่ได้มาจากฐานและทำในสิ่งที่คุณคาดหวัง™อย่างปลอดภัย (ในแง่ของอายุการใช้งาน - คุณยังต้องแน่ใจว่าประเภทฐานสามารถลบ polymorphically)


2
AFAIK นักการตลาดBaseจะไม่เรียกผู้ทำลายล้างDerivedดังนั้นฉันไม่แน่ใจว่าปลอดภัยหรือไม่ (มันไม่ได้ปลอดภัยน้อยไปกว่าตัวชี้ดิบที่ยอมรับ)
cpplearner

14

เพราะstd::unique_ptrมีคอนสตรัคเตอร์ที่แปลงเป็น

template< class U, class E >
unique_ptr( unique_ptr<U, E>&& u ) noexcept;

และ

คอนสตรัคนี้มีส่วนร่วมในการแก้ปัญหาการโอเวอร์โหลดเท่านั้นหากสิ่งต่อไปนี้ทั้งหมดเป็นจริง:

ก) unique_ptr<U, E>::pointerสามารถแปลงเป็นปริยายได้pointer

...

A Derived*สามารถแปลงเป็นBase*ปริยายได้จากนั้นคอนสตรัคเตอร์การแปลงสามารถนำไปใช้กับกรณีนี้ได้ จากนั้น a std::unique_ptr<Base>สามารถแปลงจากค่าstd::unique_ptr<Derived>ปริยายได้เช่นเดียวกับตัวชี้แบบดิบ (โปรดทราบว่าstd::unique_ptr<Derived>จะต้องมีค่าสำหรับการสร้างstd::unique_ptr<Base>เนื่องจากลักษณะของstd::unique_ptr.)


7

คุณสามารถโดยปริยายสร้างstd::unique_ptr<T>อินสแตนซ์จากrvalueของstd::unique_ptr<S>เมื่อใดก็ตามที่เป็นแปลงสภาพให้แก่S Tนี่คือสาเหตุที่คอนสตรัค # 6 ที่นี่ การเป็นเจ้าของจะถูกโอนในกรณีนี้

ในตัวอย่างของคุณคุณมีค่า rvalues ​​เท่านั้นstd::uinque_ptr<Derived>(เนื่องจากค่าส่งคืนของstd::make_uniqueคือค่า rvalue) และเมื่อคุณใช้std::unique_ptr<Base>ค่าดังกล่าวเป็น a ตัวสร้างที่กล่าวถึงข้างต้นจะถูกเรียกใช้ std::unique_ptr<Derived>วัตถุในคำถามจึงอยู่เพียงสำหรับจำนวนเงินที่สั้นของเวลาคือพวกเขาจะถูกสร้างขึ้นแล้วเจ้าของถูกส่งไปยังstd::unique_ptr<Base>วัตถุที่ใช้เพิ่มเติมเกี่ยวกับ

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