ทำไมไม่ std :: que :: pop return value.?


123

ฉันอ่านหน้านี้แต่ฉันไม่สามารถหาเหตุผลเดียวกันได้ มีการกล่าวถึงว่า

"มันสมเหตุสมผลกว่าที่จะไม่ส่งคืนค่าใด ๆ เลยและต้องการให้ไคลเอ็นต์ใช้ front () เพื่อตรวจสอบค่าที่ด้านหน้าของคิว"

แต่การตรวจสอบองค์ประกอบจาก front () จำเป็นต้องคัดลอกองค์ประกอบนั้นด้วยค่า lvalue ตัวอย่างเช่นในส่วนรหัสนี้

std::queue<int> myqueue;
int myint;
int result;
std::cin >> myint;
myqueue.push (myint);

/ * ที่นี่ชั่วคราวจะถูกสร้างขึ้นบน RHS ซึ่งจะถูกกำหนดให้กับผลลัพธ์และในกรณีที่หากส่งคืนโดยการอ้างอิงผลลัพธ์จะถูกแสดงผลไม่ถูกต้องหลังจากการดำเนินการป๊อป * /

result = myqueue.front();  //result.
std::cout << ' ' << result;
myqueue.pop();

บนวัตถุcoutบรรทัดที่ห้าก่อนสร้างสำเนา myqueue.front () จากนั้นกำหนดให้เป็นผลลัพธ์ ดังนั้นความแตกต่างคืออะไรฟังก์ชันป๊อปสามารถทำสิ่งเดียวกันได้


เนื่องจากมีการดำเนินการด้วยวิธีนี้ (กล่าวคือvoid std::queue::pop();)
101010

คำถามได้รับคำตอบแล้ว แต่เป็นข้อเสนอแนะ: หากคุณต้องการป๊อปที่ส่งคืนจริงๆก็สามารถใช้งานได้อย่างง่ายดายด้วยฟังก์ชันฟรี: ideone.com/lnUzf6
eerorika

1
ลิงก์ของคุณไปยังเอกสาร STL แต่คุณกำลังถามเกี่ยวกับไลบรารีมาตรฐาน C ++ สิ่งที่แตกต่าง.
juanchopanza

5
"แต่การตรวจสอบองค์ประกอบจากfront()จำเป็นต้องคัดลอกองค์ประกอบนั้นด้วยค่า lvalue" - ไม่ frontส่งคืนข้อมูลอ้างอิงไม่ใช่ค่า คุณสามารถตรวจสอบค่าที่อ้างถึงได้โดยไม่ต้องคัดลอก
Mike Seymour

1
@KeminZhou โมเดลที่คุณอธิบายต้องใช้สำเนา อาจจะ. หากคุณต้องการใช้คิวแบบมัลติเพล็กซ์ใช่คุณต้องทำสำเนาก่อนที่จะคลายล็อกบนคิว อย่างไรก็ตามหากคุณสนใจเฉพาะการแยกอินพุตและเอาต์พุตคุณก็ไม่จำเป็นต้องมีตัวล็อคเพื่อตรวจสอบด้านหน้า pop()คุณสามารถรอที่จะล็อคจนกว่าคุณจะทำนานและไม่จำเป็นต้องโทร หากคุณใช้std::queue<T, std::list<T>>แล้วไม่มีข้อกังวลใด ๆ เกี่ยวกับการอ้างอิงที่ให้มาจากfront()การที่ a push(). แต่คุณต้องรู้รูปแบบการใช้งานของคุณและควรบันทึกข้อ จำกัด ของคุณ
jwm

คำตอบ:


105

ดังนั้นความแตกต่างคืออะไรฟังก์ชันป๊อปสามารถทำสิ่งเดียวกันได้

มันสามารถทำสิ่งเดียวกันได้ สาเหตุที่ไม่เป็นเช่นนั้นเป็นเพราะป๊อปที่ส่งคืนองค์ประกอบที่โผล่ออกมานั้นไม่ปลอดภัยหากมีข้อยกเว้น (ต้องส่งคืนตามค่าจึงสร้างสำเนา)

ลองพิจารณาสถานการณ์นี้ (ด้วยการใช้งานแบบไร้เดียงสา / สร้างขึ้นเพื่อเพิ่มประเด็นของฉัน):

template<class T>
class queue {
    T* elements;
    std::size_t top_position;
    // stuff here
    T pop()
    {
        auto x = elements[top_position];
        // TODO: call destructor for elements[top_position] here
        --top_position;  // alter queue state here
        return x;        // calls T(const T&) which may throw
    }

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

การใช้งานนี้ไม่มีประสิทธิภาพเช่นกันในกรณีที่คุณไม่ต้องการค่าที่โผล่ขึ้นมา (กล่าวคือสร้างสำเนาขององค์ประกอบที่ไม่มีใครใช้)

สิ่งนี้สามารถดำเนินการได้อย่างปลอดภัยและมีประสิทธิภาพด้วยการดำเนินการสองอย่างแยกกัน ( void popและconst T& front())


อืม ... เข้าท่า
cbinder

37
หมายเหตุ C ++ 11: หาก T มี noexcept move-constructor ราคาถูก (ซึ่งมักเป็นกรณีของชนิดของวัตถุที่ใส่ในสแต็ก) การส่งคืนตามค่าจะมีประสิทธิภาพและปลอดภัยในข้อยกเว้น
Roman L

9
@ DavidRodríguez-dribeas: ฉันไม่ได้แนะนำการเปลี่ยนแปลงใด ๆ ประเด็นของฉันคือข้อเสียที่กล่าวถึงบางส่วนกลายเป็นปัญหาน้อยลงกับ C ++ 11
Roman L

11
แต่ทำไมถึงเรียกว่าpop? ที่ค่อนข้างสวนทางกับธรรมชาติ สามารถตั้งชื่อdropได้และเป็นที่ชัดเจนสำหรับทุกคนว่ามันไม่ได้ป๊อปองค์ประกอบ แต่ลดลงแทน ...
UeliDeSchwert

12
@utnapistim: การดำเนินการ "ป๊อป" โดยพฤตินัยคือการนำองค์ประกอบด้านบนจากกองซ้อนและส่งคืน เมื่อฉันเจอสแต็ค STL ครั้งแรกฉันรู้สึกประหลาดใจอย่างน้อยที่สุดก็คือการpopไม่ส่งคืนอะไรเลย ดูตัวอย่างในวิกิพีเดีย
Cris Luengo

34

หน้าที่คุณได้เชื่อมโยงเพื่อตอบคำถามของคุณ

หากต้องการอ้างอิงส่วนทั้งหมดที่เกี่ยวข้อง:

อาจสงสัยว่าทำไม pop () จึงคืนค่าเป็นโมฆะแทนที่จะเป็น value_type นั่นคือเหตุใดจึงต้องใช้ front () และ pop () เพื่อตรวจสอบและลบองค์ประกอบที่ด้านหน้าของคิวแทนที่จะรวมทั้งสองในฟังก์ชันสมาชิกเดียว ในความเป็นจริงมีเหตุผลที่ดีสำหรับการออกแบบนี้ หาก pop () ส่งคืนองค์ประกอบด้านหน้าก็จะต้องส่งคืนตามค่าแทนที่จะเป็นการอ้างอิง: การส่งคืนโดยการอ้างอิงจะสร้างตัวชี้ห้อย อย่างไรก็ตามการส่งคืนตามค่าไม่มีประสิทธิภาพ: เกี่ยวข้องกับการเรียกตัวสร้างสำเนาซ้ำซ้อนอย่างน้อยหนึ่งครั้ง เนื่องจากเป็นไปไม่ได้ที่ pop () จะส่งคืนค่าในลักษณะที่มีประสิทธิภาพและถูกต้องจึงเหมาะสมกว่าที่จะไม่ส่งคืนค่าใด ๆ เลยและต้องการให้ไคลเอ็นต์ใช้ front () เพื่อตรวจสอบค่าที่ ด้านหน้าของคิว

C ++ ได้รับการออกแบบโดยคำนึงถึงประสิทธิภาพโดยคำนึงถึงจำนวนบรรทัดของโค้ดที่โปรแกรมเมอร์ต้องเขียน


16
บางที แต่เหตุผลที่แท้จริงคือเป็นไปไม่ได้ที่จะใช้เวอร์ชันที่ปลอดภัยสำหรับข้อยกเว้น (พร้อมการรับประกันที่เข้มงวด) สำหรับเวอร์ชันpopที่ส่งคืนค่า
James Kanze

5

pop ไม่สามารถส่งคืนการอ้างอิงไปยังค่าที่ถูกลบออกได้เนื่องจากกำลังถูกลบออกจากโครงสร้างข้อมูลดังนั้นการอ้างอิงควรอ้างอิงถึงอะไร? มันสามารถคืนค่าได้ แต่ถ้าผลลัพธ์ของป๊อปไม่ได้ถูกเก็บไว้ที่ใด? จากนั้นก็เสียเวลาในการคัดลอกค่าโดยไม่จำเป็น


4
เหตุผลที่แท้จริงคือความปลอดภัยยกเว้น ไม่มีวิธีที่ปลอดภัยในการมี "ธุรกรรม" กับสแต็ก (องค์ประกอบยังคงอยู่บนสแต็กหรือส่งคืนให้คุณ) หากการpopส่งคืนและการส่งคืนอาจทำให้เกิดข้อยกเว้น เห็นได้ชัดว่าจะต้องลบองค์ประกอบออกก่อนที่จะส่งคืนจากนั้นหากมีบางสิ่งโยนองค์ประกอบอาจสูญหายอย่างไม่สามารถเพิกถอนได้
จอน

1
ข้อกังวลนี้ยังใช้กับ fi a noexcept move constructor หรือไม่? (แน่นอนว่า 0 สำเนามีประสิทธิภาพมากกว่าในการเคลื่อนไหวสองครั้ง แต่นั่นสามารถเปิดประตูสำหรับข้อยกเว้นที่ปลอดภัยและมีประสิทธิภาพรวมด้านหน้า + ป๊อป)
peppe

1
@peppe คุณสามารถส่งคืนตามค่าได้หากคุณรู้ว่าvalue_typeมีตัวสร้างการย้ายแบบ nothrow แต่อินเทอร์เฟซคิวจะแตกต่างกันไปขึ้นอยู่กับประเภทของวัตถุที่คุณเก็บไว้ซึ่งจะไม่เป็นประโยชน์
Jonathan Wakely

1
@peppe: นั่นจะปลอดภัย แต่สแต็กเป็นคลาสไลบรารีทั่วไปดังนั้นยิ่งต้องคำนึงถึงประเภทองค์ประกอบมากเท่าไหร่ก็ยิ่งมีประโยชน์น้อยลงเท่านั้น
จอน

ฉันรู้ว่า STL ไม่อนุญาตให้ทำเช่นนั้น แต่นั่นหมายความว่าเราสามารถสร้างคลาสคิวของตัวเองด้วยกระบองและ ^ W ^ W ^ W ด้วยคุณสมบัตินี้ :)
peppe

3

ด้วยการใช้งานปัจจุบันสิ่งนี้ใช้ได้:

int &result = myqueue.front();
std::cout << result;
myqueue.pop();

หากป๊อปจะส่งคืนข้อมูลอ้างอิงเช่นนี้:

value_type& pop();

จากนั้นรหัสต่อไปนี้อาจขัดข้องเนื่องจากการอ้างอิงไม่ถูกต้องอีกต่อไป:

int &result = myqueue.pop();
std::cout << result;

ในทางกลับกันหากจะส่งคืนค่าโดยตรง:

value_type pop();

จากนั้นคุณจะต้องทำสำเนาเพื่อให้รหัสนี้ใช้งานได้ซึ่งมีประสิทธิภาพน้อยกว่า:

int result = myqueue.pop();
std::cout << result;

1

เริ่มจาก C ++ 11 สามารถเก็บข้อมูลพฤติกรรมที่ต้องการได้โดยใช้ความหมายการย้าย ชอบpop_and_move. ดังนั้นตัวสร้างการคัดลอกจะไม่ถูกเรียกและประสิทธิภาพจะขึ้นอยู่กับตัวสร้างการย้ายเท่านั้น


2
ไม่การย้ายความหมายไม่ได้ทำให้popข้อยกเว้นปลอดภัยอย่างน่าอัศจรรย์
LF

0

คุณสามารถทำได้ทั้งหมด:

std::cout << ' ' << myqueue.front();

หรือหากคุณต้องการค่าในตัวแปรให้ใช้การอ้างอิง:

const auto &result = myqueue.front();
if (result > whatever) do_whatever();
std::cout << ' ' << result;

ถัดจากนั้นคำว่า 'สมเหตุสมผลมากขึ้น' เป็นรูปแบบอัตนัยของ 'เราได้ตรวจสอบรูปแบบการใช้งานและพบว่ามีความจำเป็นมากขึ้นในการแยก' (มั่นใจได้: ภาษา C ++ ไม่ได้มีการพัฒนาเพียงเล็กน้อย ... )


0

ฉันคิดว่าทางออกที่ดีที่สุดคือการเพิ่มสิ่งที่ชอบ

std::queue::pop_and_store(value_type& value);

โดยที่ค่าจะได้รับค่าที่โผล่มา

ข้อดีคือสามารถใช้งานได้โดยใช้ตัวดำเนินการกำหนดย้ายในขณะที่การใช้ front + pop จะทำสำเนา

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