การใช้สมาร์ทพอยน์เตอร์สำหรับสมาชิกชั้นเรียน


159

ฉันมีปัญหาในการทำความเข้าใจการใช้งานตัวชี้อัจฉริยะในฐานะสมาชิกคลาสใน C ++ 11 ฉันได้อ่านพอยน์เตอร์ที่ชาญฉลาดมากและฉันคิดว่าฉันเข้าใจวิธีการunique_ptrและshared_ptr/ หรือweak_ptrทำงานโดยทั่วไป สิ่งที่ฉันไม่เข้าใจคือการใช้งานจริง ดูเหมือนว่าทุกคนแนะนำให้ใช้unique_ptrเป็นวิธีที่จะไปเกือบตลอดเวลา แต่ฉันจะใช้บางสิ่งเช่นนี้ได้อย่างไร:

class Device {
};

class Settings {
    Device *device;
public:
    Settings(Device *device) {
        this->device = device;
    }

    Device *getDevice() {
        return device;
    }
};    

int main() {
    Device *device = new Device();
    Settings settings(device);
    // ...
    Device *myDevice = settings.getDevice();
    // do something with myDevice...
}

สมมติว่าฉันต้องการแทนที่พอยน์เตอร์ด้วยพอยน์เตอร์อัจฉริยะ unique_ptrจะไม่ทำงานเพราะgetDevice()ใช่มั้ย? ดังนั้นเวลาที่ฉันใช้shared_ptrและweak_ptr? ไม่มีวิธีใช้unique_ptr? ดูเหมือนว่าฉันจะชอบในกรณีส่วนใหญ่shared_ptrเหมาะสมกว่าถ้าฉันใช้ตัวชี้ในขอบเขตที่เล็กจริงๆ?

class Device {
};

class Settings {
    std::shared_ptr<Device> device;
public:
    Settings(std::shared_ptr<Device> device) {
        this->device = device;
    }

    std::weak_ptr<Device> getDevice() {
        return device;
    }
};

int main() {
    std::shared_ptr<Device> device(new Device());
    Settings settings(device);
    // ...
    std::weak_ptr<Device> myDevice = settings.getDevice();
    // do something with myDevice...
}

นั่นเป็นวิธีที่จะไปไหม? ขอบคุณมาก ๆ!


4
มันช่วยให้ชัดเจนจริง ๆ กับอายุการเป็นเจ้าของและโมฆะที่เป็นไปได้ ตัวอย่างเช่นเมื่อผ่านdeviceไปยังตัวสร้างของsettingsคุณต้องการที่จะยังสามารถอ้างถึงมันในขอบเขตการโทรหรือผ่านเพียงsettings? หากหลังunique_ptrมีประโยชน์ นอกจากนี้คุณมีสถานการณ์ที่ค่าตอบแทนของมีgetDevice() nullถ้าไม่ใช่เพียงแค่ส่งคืนการอ้างอิง
Keith

2
ใช่ a shared_ptrถูกต้องในกรณี 8/10 อื่น ๆ ที่ 2/10 จะแยกระหว่างและunique_ptr weak_ptrนอกจากนี้ยังweak_ptrใช้เพื่อแยกการอ้างอิงแบบวงกลม; ฉันไม่แน่ใจว่าการใช้งานของคุณจะได้รับการพิจารณาว่าถูกต้อง
Collin Dauphinee

2
ก่อนอื่นคุณต้องการความเป็นเจ้าของอะไรสำหรับdeviceข้อมูลสมาชิก คุณต้องตัดสินใจก่อนว่า
juanchopanza

1
ตกลงฉันเข้าใจว่าในฐานะผู้โทรฉันสามารถใช้unique_ptrแทนและให้สิทธิ์การเป็นเจ้าของเมื่อเรียกตัวสร้างถ้าฉันรู้ว่าตอนนี้ฉันไม่ต้องการมันอีกแล้ว แต่ในฐานะนักออกแบบของSettingsชั้นฉันไม่ทราบว่าผู้โทรต้องการเก็บข้อมูลอ้างอิงด้วยหรือไม่ อาจจะใช้อุปกรณ์ในหลาย ๆ ที่ ตกลงอาจเป็นจุดของคุณ ในกรณีนั้นฉันจะไม่เป็นเจ้าของ แต่เพียงผู้เดียวและนั่นคือเมื่อฉันจะใช้ shared_ptr ฉันเดา และ: ดังนั้นคะแนนสมาร์ทจะแทนที่ตัวชี้ แต่ไม่ใช่การอ้างอิงใช่ไหม
michaelk

this-> device = device; นอกจากนี้ยังใช้รายการเริ่มต้น
นิลส์

คำตอบ:


202

unique_ptrจะไม่ทำงานเพราะgetDevice()ใช่มั้ย?

ไม่ไม่จำเป็น สิ่งสำคัญที่นี่คือการกำหนดนโยบายความเป็นเจ้าของที่เหมาะสมสำหรับDeviceวัตถุของคุณคือใครจะเป็นเจ้าของวัตถุที่ชี้โดยตัวชี้ (สมาร์ท) ของคุณ

มันจะเป็นตัวอย่างของSettingsวัตถุเพียงอย่างเดียวหรือไม่? จะDeviceวัตถุต้องถูกทำลายโดยอัตโนมัติเมื่อSettingsวัตถุได้รับการทำลายหรือมันควรจะอายุยืนวัตถุที่?

ในกรณีแรกstd::unique_ptrคือสิ่งที่คุณต้องการเพราะมันทำให้Settingsเจ้าของวัตถุแหลม (เฉพาะ) เท่านั้นและวัตถุเดียวที่รับผิดชอบการทำลายมัน

ภายใต้สมมติฐานนี้getDevice()ควรส่งคืนพอยน์เตอร์แบบสังเกตง่าย(การพอยน์เตอร์พอยน์เตอร์เป็นพอยน์เตอร์ที่ไม่ทำให้วัตถุแหลมมีชีวิตอยู่) ชนิดของการสังเกตที่ง่ายที่สุดคือตัวชี้แบบดิบ:

#include <memory>

class Device {
};

class Settings {
    std::unique_ptr<Device> device;
public:
    Settings(std::unique_ptr<Device> d) {
        device = std::move(d);
    }

    Device* getDevice() {
        return device.get();
    }
};

int main() {
    std::unique_ptr<Device> device(new Device());
    Settings settings(std::move(device));
    // ...
    Device *myDevice = settings.getDevice();
    // do something with myDevice...
}

[ หมายเหตุ 1: คุณอาจสงสัยว่าทำไมฉันถึงใช้พอยน์เตอร์ดิบที่นี่เมื่อทุกคนบอกว่าพอยน์เตอร์ดิบนั้นไม่ดีไม่ปลอดภัยและอันตราย แต่จริงๆแล้วเป็นคำเตือนที่มีค่า แต่มันเป็นสิ่งสำคัญที่จะนำมันในบริบทที่ถูกต้อง: ตัวชี้ดิบที่ไม่ดีเมื่อนำมาใช้สำหรับการดำเนินการจัดการหน่วยความจำคู่มือเช่นการจัดสรรและ deallocating วัตถุผ่านและnew deleteเมื่อใช้อย่างหมดจดเป็นวิธีการเพื่อให้บรรลุความหมายอ้างอิงและผ่านไปรอบ ๆ ที่ไม่ได้เป็นเจ้าของสังเกตพอยน์เตอร์ไม่มีอะไรที่เป็นอันตรายภายในพอยน์เตอร์พอยน์ยกเว้นอาจเป็นเพราะความจริงที่ว่าเราควรระวัง - จบหมายเหตุ 1 ]

[ หมายเหตุ 2: ตามที่ปรากฏในความคิดเห็นในกรณีนี้โดยเฉพาะที่ความเป็นเจ้าของไม่ซ้ำกันและวัตถุที่เป็นเจ้าของจะรับประกันเสมอว่าจะนำเสนอ (เช่นสมาชิกข้อมูลภายในdeviceจะไม่เป็นไปได้nullptr) ฟังก์ชั่นgetDevice()สามารถ (และอาจจะ) ส่งคืนการอ้างอิงมากกว่าตัวชี้ ในขณะที่สิ่งนี้เป็นจริงฉันตัดสินใจคืนตัวชี้แบบดิบที่นี่เพราะฉันหมายความว่านี่เป็นคำตอบสั้น ๆ ที่คนทั่วไปสามารถพูดถึงกรณีที่deviceเป็นไปได้nullptrและเพื่อแสดงให้เห็นว่าตัวชี้แบบดิบนั้นใช้ได้ดีตราบใดที่ไม่ได้ใช้ การจัดการหน่วยความจำด้วยตนเอง - จบหมายเหตุ 2 ]


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

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

เพื่อช่วยให้คุณเข้าใจได้คุณอาจถามตัวเองว่ามีวัตถุอื่นนอกเหนือจากSettingsที่มีสิทธิ์รักษาDeviceวัตถุให้มีชีวิตอยู่หรือไม่ตราบใดที่วัตถุนั้นยังคงมีพอยน์เตอร์อยู่ หากเป็นเช่นนั้นคุณต้องมีนโยบายการเป็นเจ้าของร่วมซึ่งเป็นstd::shared_ptrข้อเสนอ:

#include <memory>

class Device {
};

class Settings {
    std::shared_ptr<Device> device;
public:
    Settings(std::shared_ptr<Device> const& d) {
        device = d;
    }

    std::shared_ptr<Device> getDevice() {
        return device;
    }
};

int main() {
    std::shared_ptr<Device> device = std::make_shared<Device>();
    Settings settings(device);
    // ...
    std::shared_ptr<Device> myDevice = settings.getDevice();
    // do something with myDevice...
}

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

ข้อดีของweak_ptrการใช้ตัวชี้ raw แบบปกติคือคุณสามารถบอกได้อย่างปลอดภัยว่าweak_ptrกำลังห้อยอยู่หรือไม่ (นั่นคือการชี้ไปยังวัตถุที่ถูกต้องหรือว่าวัตถุนั้นถูกทำลายไปแล้วในตอนแรก) สิ่งนี้สามารถทำได้โดยการเรียกexpired()ฟังก์ชั่นสมาชิกบนweak_ptrวัตถุ


4
@LKK: ใช่ถูกต้อง A weak_ptrเป็นทางเลือกสำหรับพอยน์เตอร์การสังเกตดิบ มันปลอดภัยกว่าในแง่หนึ่งเพราะคุณสามารถตรวจสอบได้ว่ามันห้อยอยู่ก่อนที่จะทำการอ้างอิงอีกครั้งหรือไม่ แต่มันมาพร้อมกับค่าใช้จ่าย หากคุณสามารถรับประกันได้อย่างง่ายดายว่าคุณจะไม่ไปหาตัวชี้ที่ห้อยต่องแต่งจากนั้นคุณควรปรับตัวชี้วัดแบบดิบ
Andy Prowl

6
ในกรณีแรกมันอาจจะดีกว่าถ้าให้getDevice()การอ้างอิงกลับมา nullptrดังนั้นผู้ที่โทรมาจะได้ไม่ต้องตรวจสอบ
vobject

5
@chico: ไม่แน่ใจว่าคุณหมายถึงอะไร auto myDevice = settings.getDevice()จะสร้างอินสแตนซ์ใหม่ของประเภทที่DeviceเรียกmyDeviceและคัดลอกสร้างจากที่อ้างอิงโดยการอ้างอิงที่getDevice()ส่งคืน หากคุณต้องการที่จะเป็นข้อมูลอ้างอิงที่คุณต้องทำmyDevice auto& myDevice = settings.getDevice()ดังนั้นถ้าฉันไม่มีอะไรหายไปเราจะกลับมาอยู่ในสถานการณ์เดียวกันกับที่เราไม่มีautoประโยชน์
Andy Prowl

2
@Peformance: เพราะคุณไม่ต้องการให้กรรมสิทธิ์ในวัตถุ - มอบให้unique_ptrกับลูกค้าที่สามารถแก้ไขได้เปิดโอกาสที่ลูกค้าจะย้ายจากมันจึงได้รับกรรมสิทธิ์และปล่อยให้คุณมีตัวชี้ null (เฉพาะ)
Andy Prowl

7
@ ประสิทธิภาพการทำงาน: ในขณะที่จะป้องกันไม่ให้ลูกค้าย้าย (ยกเว้นกรณีที่ลูกค้าเป็นนักวิทยาศาสตร์ที่คลั่งไคล้const_casts) ฉันเองจะไม่ทำมัน unique_ptrมันเสี่ยงรายละเอียดการดำเนินงานคือความจริงที่ว่าเจ้าของจะไม่ซ้ำกันและตระหนักผ่าน ฉันเห็นสิ่งต่าง ๆ ในลักษณะนี้: หากคุณต้องการ / จำเป็นต้องผ่าน / ส่งคืนความเป็นเจ้าของให้ส่ง / คืนตัวชี้สมาร์ท ( unique_ptrหรือshared_ptrขึ้นอยู่กับประเภทของความเป็นเจ้าของ) หากคุณไม่ต้องการ / จำเป็นต้องผ่าน / ส่งคืนความเป็นเจ้าของให้ใช้constตัวชี้หรือการอ้างอิง(อย่างถูกต้อง - มีคุณสมบัติเหมาะสม) ส่วนใหญ่ขึ้นอยู่กับว่าอาร์กิวเมนต์สามารถเป็นโมฆะหรือไม่
Andy Prowl

0
class Device {
};

class Settings {
    std::shared_ptr<Device> device;
public:
    Settings(const std::shared_ptr<Device>& device) : device(device) {

    }

    const std::shared_ptr<Device>& getDevice() {
        return device;
    }
};

int main()
{
    std::shared_ptr<Device> device(new Device());
    Settings settings(device);
    // ...
    std::shared_ptr<Device> myDevice(settings.getDevice());
    // do something with myDevice...
    return 0;
}

week_ptrใช้สำหรับอ้างอิงลูปเท่านั้น กราฟการขึ้นต่อกันต้องเป็นกราฟที่กำหนดทิศทางไว้ ในพอยน์เตอร์ที่แชร์มีจำนวนการอ้างอิง 2 รายการ: 1 สำหรับshared_ptrs และ 1 สำหรับตัวชี้ ( shared_ptrและweak_ptr) ทั้งหมด เมื่อshared_ptrลบ s ทั้งหมดแล้วตัวชี้จะถูกลบ เมื่อตัวชี้เป็นสิ่งจำเป็นจากweak_ptr, lockควรจะใช้ในการรับตัวชี้ถ้ามันมีอยู่


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

มีการอ้างอิงสองรายการใน a จริงshared_ptrหรือไม่ คุณช่วยอธิบายได้ไหม เท่าที่ฉันเข้าใจweak_ptrไม่จำเป็นต้องนับเพราะมันเพิ่งสร้างใหม่shared_ptrเมื่อปฏิบัติการบนวัตถุ (ถ้าวัตถุพื้นฐานยังคงมีอยู่)
Björn Pollex

@ BjörnPollex: ฉันสร้างตัวอย่างสั้น ๆ สำหรับคุณ: การเชื่อมโยง lockฉันยังไม่ได้ดำเนินการทุกอย่างเป็นเพียงแค่การคัดลอกและการก่อสร้าง บูสต์รุ่นยังเป็นเธรดที่ปลอดภัยในการนับการอ้างอิง ( deleteเรียกว่าเพียงครั้งเดียว)
Naszta

@Naszta: ตัวอย่างของคุณแสดงให้เห็นว่ามีความเป็นไปได้ที่จะใช้สิ่งนี้โดยใช้การอ้างอิงสองครั้ง แต่คำตอบของคุณแสดงให้เห็นว่านี่เป็นสิ่งจำเป็นซึ่งฉันไม่เชื่อว่ามันเป็น คุณช่วยอธิบายเรื่องนี้ในคำตอบของคุณได้ไหม?
Björn Pollex

1
@ BjörnPollexเพื่อที่weak_ptr::lock()จะบอกว่าวัตถุหมดอายุหรือไม่นั้นจะต้องตรวจสอบ "บล็อกควบคุม" ที่มีจำนวนการอ้างอิงครั้งแรกและตัวชี้ไปยังวัตถุดังนั้นบล็อกควบคุมจะต้องไม่ถูกทำลายในขณะที่มีweak_ptrวัตถุใด ๆที่ยังคงใช้อยู่ ดังนั้นจำนวนweak_ptrวัตถุต้องถูกติดตามซึ่งเป็นสิ่งที่นับอ้างอิงที่สองทำ วัตถุถูกทำลายเมื่อจำนวนการอ้างอิงแรกลดลงเป็นศูนย์บล็อกควบคุมจะถูกทำลายเมื่อจำนวนการอ้างอิงที่สองลดลงเป็นศูนย์
Jonathan Wakely
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.