ให้ฉันลองระบุโหมดการทำงานต่าง ๆ ของการส่งพอยน์เตอร์ไปยังวัตถุที่จัดการหน่วยความจำโดยอินสแตนซ์ของstd::unique_ptr
แม่แบบคลาส มันยังใช้กับstd::auto_ptr
เทมเพลตคลาสที่เก่ากว่า(ซึ่งฉันเชื่อว่าอนุญาตให้ใช้ทั้งหมดที่ตัวชี้ที่ไม่ซ้ำกันทำ แต่ในนอกจากนี้ค่า lvalues ที่ปรับเปลี่ยนได้จะได้รับการยอมรับเมื่อคาดว่าค่า rvalues โดยไม่ต้องเรียกใช้std::move
) และในระดับstd::shared_ptr
หนึ่งด้วย
เป็นตัวอย่างที่เป็นรูปธรรมสำหรับการอภิปรายฉันจะพิจารณาประเภทรายการง่าย ๆ ดังต่อไปนี้
struct node;
typedef std::unique_ptr<node> list;
struct node { int entry; list next; }
อินสแตนซ์ของรายการดังกล่าว (ซึ่งไม่สามารถได้รับอนุญาตให้แบ่งปันชิ้นส่วนกับอินสแตนซ์อื่นหรือเป็นแบบวงกลม) เป็นเจ้าของทั้งหมดโดยผู้ที่ถือlist
ตัวชี้เริ่มต้น ถ้ารหัสลูกค้ารู้ว่ารายการที่ร้านค้าจะไม่ว่างก็อาจเลือกที่จะเก็บครั้งแรกโดยตรงมากกว่าnode
list
ไม่node
จำเป็นต้องกำหนด destructor: เนื่องจาก destructors สำหรับเขตข้อมูลจะถูกเรียกโดยอัตโนมัติรายการทั้งหมดจะถูกลบซ้ำโดย destructor ตัวชี้อัจฉริยะเมื่ออายุการใช้งานของตัวชี้หรือโหนดเริ่มต้นสิ้นสุดลง
ประเภทเรียกซ้ำนี้ให้โอกาสในการหารือเกี่ยวกับบางกรณีที่มองเห็นได้น้อยลงในกรณีของตัวชี้สมาร์ทกับข้อมูลธรรมดา ฟังก์ชั่นเองก็มีตัวอย่างของรหัสลูกค้า (ซ้ำ) อีกด้วย typedef list
ของหลักสูตรมีอคติต่อunique_ptr
แต่คำจำกัดความสามารถเปลี่ยนเป็นการใช้auto_ptr
หรือshared_ptr
แทนโดยไม่จำเป็นต้องเปลี่ยนสิ่งที่ได้กล่าวไว้ด้านล่าง
โหมดการผ่านตัวชี้อัจฉริยะรอบ ๆ
โหมด 0: ผ่านตัวชี้หรืออาร์กิวเมนต์อ้างอิงแทนตัวชี้อัจฉริยะ
หากฟังก์ชั่นของคุณไม่เกี่ยวข้องกับความเป็นเจ้าของนี่เป็นวิธีที่ต้องการ: อย่าทำให้มันใช้ตัวชี้สมาร์ทเลย ในกรณีนี้ฟังก์ชั่นของคุณไม่จำเป็นต้องกังวลว่าใครเป็นเจ้าของวัตถุที่ชี้ไปหรือด้วยการจัดการความเป็นเจ้าของดังนั้นการส่งตัวชี้แบบดิบจึงปลอดภัยอย่างสมบูรณ์และรูปแบบที่ยืดหยุ่นที่สุดเนื่องจากไม่ว่าลูกค้าจะเป็นเจ้าของได้ก็ตาม สร้างตัวชี้ raw (โดยการเรียกใช้get
เมธอดหรือจาก address-of operator &
)
เช่นฟังก์ชั่นในการคำนวณความยาวของรายการดังกล่าวไม่ควรให้list
ข้อโต้แย้ง แต่เป็นตัวชี้แบบดิบ:
size_t length(const node* p)
{ size_t l=0; for ( ; p!=nullptr; p=p->next.get()) ++l; return l; }
ลูกค้าที่ถือตัวแปรlist head
สามารถเรียกฟังก์ชั่นนี้เป็นlength(head.get())
ในขณะที่ลูกค้าที่ได้รับการแต่งตั้งแทนการจัดเก็บที่เป็นตัวแทนของรายการที่ไม่ว่างเปล่าสามารถเรียกnode n
length(&n)
หากตัวชี้มีการรับประกันว่าจะไม่เป็นโมฆะ (ซึ่งไม่ใช่ในกรณีนี้เนื่องจากรายการอาจว่างเปล่า) หนึ่งอาจต้องการผ่านการอ้างอิงมากกว่าตัวชี้ มันอาจจะเป็นตัวชี้ / อ้างอิงถึงไม่ใช่const
ถ้าฟังก์ชั่นจำเป็นต้องปรับปรุงเนื้อหาของโหนดโดยไม่ต้องเพิ่มหรือลบใด ๆ ของพวกเขา (หลังจะเกี่ยวข้องกับความเป็นเจ้าของ)
กรณีที่น่าสนใจที่อยู่ในหมวดหมู่โหมด 0 คือการทำสำเนา (ลึก) ของรายการ ในขณะที่ฟังก์ชั่นที่ทำสิ่งนี้จะต้องถ่ายโอนความเป็นเจ้าของสำเนาที่สร้างขึ้น แต่ไม่เกี่ยวข้องกับความเป็นเจ้าของรายการที่กำลังคัดลอก ดังนั้นจึงสามารถกำหนดได้ดังนี้
list copy(const node* p)
{ return list( p==nullptr ? nullptr : new node{p->entry,copy(p->next.get())} ); }
รหัสนี้มองใกล้ทั้งคำถามที่ว่าทำไมมันรวบรวมเลย (ผลของการเรียกซ้ำcopy
ในรายการเริ่มต้นผูกกับอาร์กิวเมนต์อ้างอิง rvalue ในตัวสร้างการย้ายของunique_ptr<node>
อาคาlist
เมื่อเริ่มต้นnext
สนามของ สร้างnode
) และสำหรับคำถามที่ว่าทำไมมันเป็นข้อยกเว้นปลอดภัย (หากในระหว่างหน่วยความจำขั้นตอนการจัดสรร recursive หมดและบางคนเรียกการnew
พ่นstd::bad_alloc
แล้วในเวลานั้นตัวชี้ไปยังรายการที่สร้างส่วนหนึ่งที่จะจัดขึ้นในนามชั่วคราวประเภทlist
สร้างขึ้นสำหรับรายการผู้เริ่มต้นและตัวทำลายมันจะล้างรายการบางส่วนนั้น) โดยวิธีการหนึ่งควรต่อต้านการทดลองที่จะเปลี่ยน (ตามที่ผมเริ่มได้) ครั้งที่สองnullptr
โดยp
ซึ่งหลังจากทั้งหมดทราบว่าเป็นโมฆะ ณ จุดนั้น: หนึ่งไม่สามารถสร้างตัวชี้สมาร์ทจากตัวชี้ (ดิบ) ถึงค่าคงที่แม้ว่ามันจะเป็นโมฆะ
โหมด 1: ผ่านตัวชี้สมาร์ทตามค่า
ฟังก์ชั่นที่ใช้ค่าตัวชี้สมาร์ทเป็นข้อโต้แย้งครอบครองวัตถุที่ชี้ไปทันที: ตัวชี้สมาร์ทที่โทรเข้า (ไม่ว่าจะอยู่ในตัวแปรชื่อหรือชั่วคราวที่ไม่ระบุชื่อ) จะถูกคัดลอกลงในค่าอาร์กิวเมนต์ที่ทางเข้าฟังก์ชันและผู้โทร ตัวชี้กลายเป็นโมฆะ (ในกรณีของชั่วคราวสำเนาอาจถูกลบออกไป แต่ในกรณีใด ๆ ที่ผู้โทรได้สูญเสียการเข้าถึงไปยังวัตถุที่ชี้ไปที่) ฉันต้องการโทรหาโหมดนี้ด้วยเงินสด : ผู้โทรจ่ายเงินล่วงหน้าสำหรับบริการที่เรียกว่าและไม่สามารถมีภาพลวงตาเกี่ยวกับความเป็นเจ้าของหลังจากการโทร เพื่อให้ชัดเจนกฎภาษาจำเป็นต้องมีผู้โทรเพื่อตัดอาร์กิวเมนต์std::move
ถ้าตัวชี้สมาร์ทถูกเก็บไว้ในตัวแปร (ในทางเทคนิคถ้าอาร์กิวเมนต์เป็น lvalue); ในกรณีนี้ (แต่ไม่ใช่สำหรับโหมด 3 ด้านล่าง) ฟังก์ชั่นนี้ทำในสิ่งที่ชื่อแนะนำคือย้ายค่าจากตัวแปรไปยังชั่วคราวโดยปล่อยตัวแปรว่างไว้
สำหรับกรณีที่ฟังก์ชั่นที่เรียกโดยไม่มีเงื่อนไขจะเป็นเจ้าของ (pilfers) วัตถุแบบชี้ไปที่โหมดนี้ใช้กับstd::unique_ptr
หรือstd::auto_ptr
เป็นวิธีที่ดีในการส่งตัวชี้พร้อมกับความเป็นเจ้าของซึ่งจะช่วยลดความเสี่ยงต่อการรั่วไหลของหน่วยความจำ อย่างไรก็ตามฉันคิดว่ามีเพียงไม่กี่สถานการณ์เท่านั้นที่ไม่ควรเลือกใช้โหมด 3 ด้านล่าง (เหนือกว่าเล็กน้อย) ในโหมด 1 ด้วยเหตุนี้ฉันจะไม่แสดงตัวอย่างการใช้งานของโหมดนี้ (แต่ดูreversed
ตัวอย่างของโหมด 3 ด้านล่างซึ่งมีการตั้งข้อสังเกตว่าโหมด 1 จะทำอย่างน้อยเช่นกัน) หากฟังก์ชันใช้อาร์กิวเมนต์มากกว่าตัวชี้นี้อาจเกิดขึ้นได้ว่ามีเหตุผลทางเทคนิคเพิ่มเติมเพื่อหลีกเลี่ยงโหมด 1 (พร้อมstd::unique_ptr
หรือstd::auto_ptr
): เนื่องจากการดำเนินการย้ายที่แท้จริงเกิดขึ้นขณะผ่านตัวแปรตัวชี้p
โดยนิพจน์std::move(p)
จะไม่สามารถสันนิษฐานได้ว่าp
มีค่าที่มีประโยชน์ในขณะที่ประเมินอาร์กิวเมนต์อื่น ๆ (ลำดับของการประเมินผลไม่ได้ระบุ) ซึ่งอาจนำไปสู่ข้อผิดพลาดเล็กน้อย โดยคมชัดใช้โหมด 3 มั่นใจว่าจะไม่มีการย้ายจากp
ที่เกิดขึ้นก่อนที่จะเรียกฟังก์ชั่นเพื่อการขัดแย้งอื่น ๆ p
สามารถเข้าถึงค่าผ่านทางได้อย่างปลอดภัย
เมื่อใช้กับstd::shared_ptr
โหมดนี้มีความน่าสนใจด้วยการกำหนดฟังก์ชั่นเดียวที่ช่วยให้ผู้โทรเลือกว่าจะเก็บสำเนาการแชร์ของตัวชี้ไว้หรือไม่ในขณะที่สร้างสำเนาการแบ่งปันใหม่ที่จะใช้โดยฟังก์ชัน (เกิดขึ้นเมื่อ lvalue มีการจัดเตรียมตัวสร้างการคัดลอกสำหรับพอยน์เตอร์ที่ใช้ร่วมกันที่ใช้ในการโทรเพิ่มจำนวนการอ้างอิง) หรือเพียงแค่ให้ฟังก์ชั่นการคัดลอกของตัวชี้โดยไม่ต้องเก็บหนึ่งหรือสัมผัสนับอ้างอิง (เกิดขึ้นเมื่ออาร์กิวเมนต์ rvalue อาจเป็นไปได้ lvalue ห่อด้วยการเรียกstd::move
) ตัวอย่างเช่น
void f(std::shared_ptr<X> x) // call by shared cash
{ container.insert(std::move(x)); } // store shared pointer in container
void client()
{ std::shared_ptr<X> p = std::make_shared<X>(args);
f(p); // lvalue argument; store pointer in container but keep a copy
f(std::make_shared<X>(args)); // prvalue argument; fresh pointer is just stored away
f(std::move(p)); // xvalue argument; p is transferred to container and left null
}
สามารถทำได้โดยการกำหนดแยกต่างหากvoid f(const std::shared_ptr<X>& x)
(สำหรับกรณี lvalue) และvoid f(std::shared_ptr<X>&& x)
(สำหรับกรณี rvalue) โดยที่ฟังก์ชันของฟังก์ชันต่างกันเฉพาะในรุ่นแรกที่เรียกใช้สำเนาความหมาย (ใช้การสร้างสำเนา / การกำหนดเมื่อใช้x
) แต่รุ่นที่สองย้าย semantics (เขียนstd::move(x)
แทนเช่นในรหัสตัวอย่าง) ดังนั้นสำหรับพอยน์เตอร์ที่ใช้ร่วมกันโหมด 1 จึงมีประโยชน์ในการหลีกเลี่ยงการทำซ้ำโค้ด
โหมด 2: ผ่านตัวชี้สมาร์ทโดยอ้างอิง lvalue (แก้ไขได้)
ที่นี่ฟังก์ชั่นเพียงแค่ต้องมีการอ้างอิงแก้ไขได้กับตัวชี้สมาร์ท แต่ไม่ได้บ่งชี้ว่ามันจะทำอะไรกับมัน ฉันต้องการโทรหาวิธีนี้โทรด้วยบัตร : ผู้โทรยืนยันการชำระเงินโดยแจ้งหมายเลขบัตรเครดิต การอ้างอิงสามารถใช้ในการเป็นเจ้าของวัตถุชี้ไปที่ แต่ไม่จำเป็นต้อง โหมดนี้ต้องการการจัดเตรียมอาร์กิวเมนต์ lvalue ที่แก้ไขได้ซึ่งสอดคล้องกับความจริงที่ว่าผลที่ต้องการของฟังก์ชันอาจรวมถึงการทิ้งค่าที่มีประโยชน์ในตัวแปรอาร์กิวเมนต์ ผู้เรียกที่มีนิพจน์ rvalue ที่ต้องการส่งไปยังฟังก์ชันดังกล่าวจะถูกบังคับให้เก็บไว้ในตัวแปรที่กำหนดชื่อเพื่อให้สามารถโทรได้เนื่องจากภาษาให้การแปลงโดยนัยเป็นค่าคงที่เท่านั้นการอ้างอิง lvalue (อ้างอิงถึงชั่วคราว) จาก rvalue (ไม่เหมือนกับสถานการณ์ตรงกันข้ามที่จัดการโดยstd::move
ไม่สามารถใช้การส่งจากY&&
ไปยังY&
พร้อมกับY
ตัวชี้แบบสมาร์ทอย่างไรก็ตามการแปลงนี้สามารถรับได้โดยฟังก์ชั่นเทมเพลตอย่างง่ายหากต้องการจริงๆดูhttps://stackoverflow.com/a/24868376 / 1436796 ) สำหรับกรณีที่ฟังก์ชั่นที่เรียกว่าตั้งใจที่จะเป็นเจ้าของวัตถุโดยไม่มีเงื่อนไขขโมยจากการโต้เถียงภาระหน้าที่ในการให้ข้อโต้แย้ง lvalue ให้สัญญาณผิด: ตัวแปรจะไม่มีค่าที่มีประโยชน์หลังจากการโทร ดังนั้นโหมด 3 ซึ่งให้ความเป็นไปได้ที่เหมือนกันในฟังก์ชั่นของเรา แต่ขอให้ผู้โทรแจ้งค่า rvalue ควรเป็นที่ต้องการสำหรับการใช้งานดังกล่าว
แต่มีกรณีที่ใช้ที่ถูกต้องสำหรับ 2 โหมดคือฟังก์ชั่นที่อาจแก้ไขตัวชี้หรือวัตถุที่ชี้ไปในทางที่เกี่ยวข้องกับการเป็นเจ้าของ ตัวอย่างเช่นฟังก์ชันที่นำหน้าโหนดไปยัง a list
เป็นตัวอย่างของการใช้งานดังกล่าว:
void prepend (int x, list& l) { l = list( new node{ x, std::move(l)} ); }
เห็นได้ชัดว่ามันจะไม่เป็นที่พึงปรารถนาที่นี่เพื่อบังคับให้ผู้โทรใช้std::move
เนื่องจากตัวชี้สมาร์ทของพวกเขายังคงเป็นเจ้าของรายการที่กำหนดไว้และไม่ว่างหลังจากการโทร
อีกครั้งเป็นที่น่าสนใจที่จะสังเกตว่าเกิดอะไรขึ้นถ้าการprepend
โทรล้มเหลวเนื่องจากไม่มีหน่วยความจำว่าง จากนั้นnew
สายจะโยนstd::bad_alloc
; ณ จุดนี้ในเวลาเนื่องจากไม่node
สามารถจัดสรรได้เป็นที่แน่นอนว่าการอ้างอิงค่า rvalue ที่ผ่าน (โหมด 3) จากstd::move(l)
ยังไม่สามารถถูกขโมยได้ซึ่งจะทำเพื่อสร้างnext
สนามของnode
ที่ไม่สามารถจัดสรรได้ ดังนั้นตัวชี้สมาร์ทดั้งเดิมl
ยังคงถือรายการเดิมเมื่อข้อผิดพลาดจะถูกโยน; รายการนั้นจะถูกทำลายอย่างถูกต้องโดยตัวทำลายสมาร์ทพอยน์เตอร์หรือในกรณีที่l
จะอยู่รอดได้ต้องขอบคุณcatch
ประโยคแรกที่เพียงพอ แต่จะยังคงอยู่ในรายการเดิม
นั่นเป็นตัวอย่างที่สร้างสรรค์ ด้วยวิ้งค์คำถามนี้เราสามารถให้ตัวอย่างที่เป็นอันตรายมากกว่าในการลบโหนดแรกที่มีค่าที่กำหนดถ้ามี:
void remove_first(int x, list& l)
{ list* p = &l;
while ((*p).get()!=nullptr and (*p)->entry!=x)
p = &(*p)->next;
if ((*p).get()!=nullptr)
(*p).reset((*p)->next.release()); // or equivalent: *p = std::move((*p)->next);
}
ความถูกต้องอีกครั้งค่อนข้างบอบบางที่นี่ โดยเฉพาะอย่างยิ่งในคำสั่งสุดท้ายตัวชี้ที่(*p)->next
อยู่ภายในโหนดที่จะลบนั้นไม่ได้เชื่อมโยง (โดยrelease
จะคืนค่าตัวชี้ แต่สร้างโมฆะดั้งเดิม) ก่อน reset
(โดยปริยาย) จะทำลายโหนดนั้น (เมื่อมันทำลายค่าเก่าที่ถือโดยp
) หนึ่งและหนึ่งโหนดเท่านั้นที่ถูกทำลายในเวลานั้น (ในรูปแบบทางเลือกที่กล่าวถึงในความคิดเห็นช่วงเวลานี้จะถูกปล่อยให้อยู่ใน internals ของการดำเนินการของผู้ประกอบการย้ายที่ได้รับมอบหมายของstd::unique_ptr
อินสแตนซ์list
มาตรฐานพูดว่า 20.7.1.2.3; 2 ว่าผู้ประกอบการนี้ควรกระทำ "ราวกับว่า เรียกreset(u.release())
"ดังนั้นเวลาควรปลอดภัยที่นี่ด้วย)
โปรดทราบว่าprepend
และremove_first
ไม่สามารถเรียกใช้โดยลูกค้าที่เก็บnode
ตัวแปรท้องถิ่นสำหรับรายการที่ไม่ว่างเสมอและถูกต้องดังนั้นเนื่องจากการใช้งานที่ให้ไว้ไม่สามารถใช้งานได้สำหรับกรณีดังกล่าว
โหมด 3: ผ่านตัวชี้สมาร์ทโดยอ้างอิงค่า rvalue (แก้ไขได้)
นี่คือโหมดที่ต้องการใช้เมื่อเพียงแค่เป็นเจ้าของพอยน์เตอร์ ฉันต้องการเรียกวิธีการนี้โดยใช้เช็ค : ผู้โทรต้องยอมรับการเป็นเจ้าของราวกับให้เงินสดโดยเซ็นชื่อในเช็ค แต่การถอนเงินจริงจะถูกเลื่อนออกไปจนกว่าฟังก์ชั่นที่เรียกจะใช้ตัวชี้ (ตามที่ใช้โหมด 2 ) "การลงนามในเช็ค" เป็นรูปธรรมหมายถึงผู้โทรต้องตัดอาร์กิวเมนต์ในstd::move
(เช่นในโหมด 1) หากเป็น lvalue (หากเป็น rvalue ส่วน "การให้สิทธิ์การเป็นเจ้าของ" จะเห็นได้ชัดและไม่ต้องใช้รหัสแยกต่างหาก)
โปรดทราบว่าในทางเทคนิคโหมด 3 ทำงานตรงกับโหมด 2 ดังนั้นฟังก์ชั่นที่เรียกว่าไม่จำเป็นต้องถือว่าเป็นเจ้าของ; อย่างไรก็ตามฉันขอยืนยันว่าหากมีความไม่แน่นอนเกี่ยวกับการถ่ายโอนความเป็นเจ้าของ (ในการใช้งานปกติ) ควรเลือกโหมด 2 เป็นโหมด 3 เพื่อให้การใช้โหมด 3 เป็นสัญญาณที่บ่งบอกว่าพวกเขาเป็นเจ้าของ หนึ่งอาจโต้กลับว่าโหมด 1 ข้อโต้แย้งผ่านสัญญาณจริง ๆ บังคับให้สูญเสียความเป็นเจ้าของแก่ผู้โทร แต่ถ้าลูกค้ามีข้อสงสัยเกี่ยวกับความตั้งใจของฟังก์ชั่นที่เรียกเธอควรจะรู้ว่าข้อกำหนดของฟังก์ชั่นที่ถูกเรียกซึ่งควรลบข้อสงสัยใด ๆ
เป็นการยากที่จะหาตัวอย่างทั่วไปที่เกี่ยวข้องกับlist
ประเภทของเราที่ใช้การส่งผ่านข้อโต้แย้งโหมด 3 อย่างน่าประหลาดใจ การย้ายรายการb
ไปยังจุดสิ้นสุดของรายการอื่นa
เป็นตัวอย่างทั่วไป อย่างไรก็ตามa
(ซึ่งยังมีชีวิตอยู่และเก็บผลลัพธ์ของการดำเนินการ) จะผ่านไปได้ดีกว่าโดยใช้โหมด 2:
void append (list& a, list&& b)
{ list* p=&a;
while ((*p).get()!=nullptr) // find end of list a
p=&(*p)->next;
*p = std::move(b); // attach b; the variable b relinquishes ownership here
}
ตัวอย่างที่แท้จริงของการส่งผ่านอาร์กิวเมนต์โหมด 3 คือรายการที่รับ (และความเป็นเจ้าของ) และส่งคืนรายการที่มีโหนดที่เหมือนกันในลำดับย้อนกลับ
list reversed (list&& l) noexcept // pilfering reversal of list
{ list p(l.release()); // move list into temporary for traversal
list result(nullptr);
while (p.get()!=nullptr)
{ // permute: result --> p->next --> p --> (cycle to result)
result.swap(p->next);
result.swap(p);
}
return result;
}
ฟังก์ชั่นนี้อาจถูกเรียกว่าเป็นในl = reversed(std::move(l));
การย้อนกลับรายการเป็นของตัวเอง แต่รายการกลับรายการสามารถใช้ที่แตกต่างกัน
ที่นี่อาร์กิวเมนต์ถูกย้ายไปยังตัวแปรโลคัลทันทีเพื่อประสิทธิภาพ (หนึ่งสามารถใช้พารามิเตอร์l
โดยตรงในสถานที่p
แต่จากนั้นการเข้าถึงมันในแต่ละครั้งจะเกี่ยวข้องกับระดับพิเศษของการอ้อม); ดังนั้นความแตกต่างกับการส่งผ่านอาร์กิวเมนต์โหมด 1 จึงน้อยที่สุด ในความเป็นจริงโดยใช้โหมดนั้นอาร์กิวเมนต์อาจทำหน้าที่เป็นตัวแปรท้องถิ่นโดยตรงดังนั้นจึงหลีกเลี่ยงการย้ายครั้งแรก นี่เป็นเพียงตัวอย่างของหลักการทั่วไปที่ถ้าอาร์กิวเมนต์ที่ส่งผ่านโดยการอ้างอิงทำหน้าที่เริ่มต้นตัวแปรท้องถิ่นเพียงอย่างเดียวอาจส่งผ่านค่านั้นโดยใช้ค่าแทนและใช้พารามิเตอร์เป็นตัวแปรท้องถิ่น
การใช้โหมด 3 ดูเหมือนจะสนับสนุนโดยมาตรฐานเท่าที่เห็นจากข้อเท็จจริงที่ว่าฟังก์ชั่นทั้งหมดให้ห้องสมุดที่โอนกรรมสิทธิ์ของตัวชี้สมาร์ทใช้โหมด 3. std::shared_ptr<T>(auto_ptr<T>&& p)
กรณีพิเศษเชื่อในจุดคือสร้าง คอนสตรัคเตอร์นั้นใช้ (ในstd::tr1
) เพื่อทำการอ้างอิงlvalue ที่แก้ไขได้(เช่นเดียวกับตัวauto_ptr<T>&
สร้างการคัดลอก) และสามารถเรียกได้ด้วยauto_ptr<T>
lvalue p
เช่นเดียวกับในstd::shared_ptr<T> q(p)
หลังจากp
นั้นถูกรีเซ็ตเป็น null เนื่องจากการเปลี่ยนแปลงจากโหมด 2 เป็น 3 ในการผ่านการโต้แย้งตอนนี้รหัสเก่าจะต้องถูกเขียนใหม่std::shared_ptr<T> q(std::move(p))
และจะยังคงทำงานต่อไป ฉันเข้าใจว่าคณะกรรมการไม่ชอบโหมด 2 ที่นี่ แต่พวกเขามีตัวเลือกในการเปลี่ยนเป็นโหมด 1 โดยกำหนดstd::shared_ptr<T>(auto_ptr<T> p)
แต่พวกเขาสามารถมั่นใจได้ว่าโค้ดเก่าทำงานโดยไม่มีการดัดแปลงเพราะ (ไม่เหมือนกับพอยน์เตอร์ที่ไม่ซ้ำกัน) พอยน์เตอร์อัตโนมัติสามารถทำการตรวจสอบค่าได้อย่างเงียบ ๆ (วัตถุตัวชี้จะถูกรีเซ็ตเป็น null ในกระบวนการ เห็นได้ชัดว่าคณะกรรมการที่ต้องการให้การสนับสนุนโหมด 3 มากกว่าโหมด 1 พวกเขาเลือกที่จะทำลายรหัสที่มีอยู่อย่างแข็งขันมากกว่าที่จะใช้โหมด 1 แม้สำหรับการใช้งานที่เลิกใช้แล้ว
เมื่อใดที่ต้องการโหมด 3 มากกว่าโหมด 1
โหมด 1 สามารถใช้งานได้อย่างสมบูรณ์แบบในหลายกรณีและอาจได้รับความนิยมมากกว่าโหมด 3 ในกรณีที่สมมติว่าความเป็นเจ้าของจะใช้รูปแบบของการย้ายตัวชี้สมาร์ทไปยังตัวแปรท้องถิ่นตามreversed
ตัวอย่างด้านบน อย่างไรก็ตามฉันเห็นเหตุผลสองประการที่ต้องการโหมด 3 ในกรณีทั่วไปมากขึ้น:
มันมีประสิทธิภาพมากกว่าเล็กน้อยในการผ่านการอ้างอิงกว่าการสร้างชั่วคราวและระวังตัวชี้เก่า (การจัดการเงินสดค่อนข้างลำบาก) ในบางสถานการณ์ตัวชี้อาจถูกส่งผ่านหลายครั้งไม่เปลี่ยนแปลงไปยังฟังก์ชั่นอื่นก่อนที่จะถูกขโมยจริง การส่งผ่านดังกล่าวโดยทั่วไปจะต้องมีการเขียนstd::move
(เว้นแต่ใช้โหมด 2) แต่โปรดทราบว่านี่เป็นเพียงนักแสดงที่ไม่ได้ทำอะไรเลย
ควรเป็นไปได้หรือไม่ว่าสิ่งใดจะทำให้เกิดข้อยกเว้นระหว่างจุดเริ่มต้นของการเรียกฟังก์ชันและจุดที่ (หรือบางสายที่มีอยู่) เคลื่อนย้ายวัตถุที่ชี้ไปยังโครงสร้างข้อมูลอื่น (และข้อยกเว้นนี้ไม่ได้ติดอยู่ภายในตัวฟังก์ชันเอง ) จากนั้นเมื่อใช้โหมด 1 วัตถุที่อ้างถึงโดยตัวชี้สมาร์ทจะถูกทำลายก่อนที่catch
ข้อสามารถจัดการข้อยกเว้น (เพราะพารามิเตอร์ฟังก์ชั่นถูกทำลายในระหว่างการคลี่คลายคลี่คลาย) แต่ไม่ได้ดังนั้นเมื่อใช้โหมด 3 ผู้เรียกมีตัวเลือกในการกู้คืนข้อมูลของวัตถุในกรณีดังกล่าว (โดยการตรวจจับข้อยกเว้น) โปรดทราบว่าโหมด 1 ที่นี่ไม่ทำให้หน่วยความจำรั่วแต่อาจนำไปสู่การสูญเสียข้อมูลที่ไม่สามารถกู้คืนได้สำหรับโปรแกรมซึ่งอาจไม่เป็นที่ต้องการเช่นกัน
กลับมาเป็นตัวชี้สมาร์ท: ตามค่าเสมอ
เพื่อสรุปคำศัพท์เกี่ยวกับการคืนค่าพอยน์เตอร์สมาร์ทให้ชี้ไปที่วัตถุที่สร้างขึ้นเพื่อใช้โดยผู้โทร นี่ไม่ใช่กรณีที่เทียบได้กับการส่งพอยน์เตอร์ไปยังฟังก์ชั่น แต่เพื่อความสมบูรณ์ฉันขอยืนยันว่าในกรณีเช่นนี้จะคืนค่าเสมอ (และไม่ได้ใช้ std::move
ในreturn
คำสั่ง) ไม่มีใครต้องการได้รับการอ้างอิงไปยังตัวชี้ที่อาจเพิ่งได้รับการผสม