สมาชิกชั้นอ้างอิง const ยืดอายุการใช้งานของชั่วคราวหรือไม่?


171

ทำไมสิ่งนี้:

#include <string>
#include <iostream>
using namespace std;

class Sandbox
{
public:
    Sandbox(const string& n) : member(n) {}
    const string& member;
};

int main()
{
    Sandbox sandbox(string("four"));
    cout << "The answer is: " << sandbox.member << endl;
    return 0;
}

ให้ผลลัพธ์จาก:

คำตอบคือ:

แทน:

คำตอบคือ: สี่


39
และเพื่อความสนุกสนานมากขึ้นถ้าคุณเขียนcout << "The answer is: " << Sandbox(string("four")).member << endl;มันก็จะรับประกันได้ว่าจะทำงาน

7
@RogerPate คุณช่วยอธิบายสาเหตุได้ไหม
เปาโล M

16
สำหรับคนที่อยากรู้อยากเห็นตัวอย่างเช่นโรเจอร์กบาลโพสต์ผลงานเพราะสตริง ( "สี่")เป็นการชั่วคราวและที่ชั่วคราวถูกทำลายในตอนท้ายของ expresion เต็มดังนั้นในตัวอย่างของเขาเมื่อSandBox::memberจะอ่านสตริงชั่วคราวยังมีชีวิตอยู่
PcAF

1
คำถามคือ: เนื่องจากการเขียนคลาสดังกล่าวเป็นสิ่งที่อันตรายมีคำเตือนคอมไพเลอร์ต่อการส่งผ่านทางขมับไปยังชั้นเรียนดังกล่าวหรือมีแนวทางการออกแบบ (ใน Stroustroup?) ที่ห้ามเขียนชั้นเรียนที่เก็บการอ้างอิง? แนวทางการออกแบบเพื่อเก็บพอยน์เตอร์แทนที่จะเป็นการอ้างอิงจะดีกว่า
Grim Fandango

@PAFAF: คุณช่วยอธิบายได้ไหมว่าทำไมชั่วคราวstring("four")ถูกทำลายในตอนท้ายของการแสดงออกเต็มรูปแบบและไม่ใช่หลังจากตัวSandboxสร้างออก? คำตอบของ Potatoswatter กล่าวว่ามีข้อผูกมัดชั่วคราวกับสมาชิกอ้างอิงใน ctor-initializer ของ constructor (§12.6.2 [class.base.init]) ยังคงมีอยู่จนกว่าตัวสร้างจะออก
เทย์เลอร์นิโคลส์

คำตอบ:


166

การอ้างอิงในท้องถิ่น เท่านั้นที่จะconstยืดอายุการใช้งาน

มาตรฐานระบุพฤติกรรมดังกล่าวใน§8.5.3 / 5, [dcl.init.ref] ส่วนที่เกี่ยวกับการเริ่มต้นของการประกาศอ้างอิง การอ้างอิงในตัวอย่างของคุณถูกผูกไว้กับอาร์กิวเมนต์ของตัวสร้างnและจะไม่ถูกต้องเมื่อวัตถุnถูกผูกไว้ที่จะออกนอกขอบเขต

ส่วนขยายอายุการใช้งานไม่ได้ผ่านการส่งผ่านอาร์กิวเมนต์ของฟังก์ชัน §12.2 / 5 [class.temporary]:

บริบทที่สองคือเมื่อการอ้างอิงถูกผูกไว้กับชั่วคราว ชั่วคราวที่อ้างอิงถูกผูกไว้หรือชั่วคราวที่เป็นวัตถุที่สมบูรณ์เพื่อ subobject ที่ชั่วคราวถูกผูกไว้ยังคงมีอยู่ตลอดชีวิตของการอ้างอิงยกเว้นตามที่ระบุไว้ด้านล่าง การผูกชั่วคราวกับสมาชิกอ้างอิงใน ctor-initializer ของตัวสร้าง (§12.6.2 [class.base.init]) ยังคงอยู่จนกว่าตัวสร้างจะออก ชั่วคราวที่ผูกไว้กับพารามิเตอร์อ้างอิงในการเรียกใช้ฟังก์ชัน (§5.2.2 [expr.call]) ยังคงมีอยู่จนกว่านิพจน์เต็มที่มีการโทรจะเสร็จสิ้น


49
คุณควรดู GotW # 88 สำหรับคำอธิบายที่เป็นมิตรกับมนุษย์มากขึ้น: herbutter.com/2008/01/01/…
นาธานเอิร์นส์

1
ฉันคิดว่ามันจะชัดเจนขึ้นถ้ามาตรฐานกล่าวว่า "บริบทที่สองคือเมื่อการอ้างอิงถูกผูกมัดกับค่าที่เหมาะสม" ในรหัสของ OP คุณสามารถพูดได้ว่าmemberถูกผูกไว้กับชั่วคราวเพราะเริ่มต้นmemberด้วยnวิธีการผูกmemberกับวัตถุเดียวกันnถูกผูกไว้กับและในความเป็นจริงเป็นวัตถุชั่วคราวในกรณีนี้
MM

2
@MM มีหลายกรณีที่ initializer lvalue หรือ xvalue ที่มี prvalue จะขยาย prvalue กระดาษข้อเสนอของฉันP0066ทบทวนสถานะของกิจการ
Potatoswatter

1
ในฐานะของ C ++ 11 การอ้างอิง Rvalue ยังช่วยยืดอายุการใช้งานของชั่วคราวโดยไม่ต้องใช้ตัวconstแปลง
ฟรี

3
@KeNVinFavo ใช่การใช้วัตถุที่ตายแล้วอยู่เสมอ UB
Potatoswatter

30

นี่คือวิธีที่ง่ายที่สุดในการอธิบายสิ่งที่เกิดขึ้น:

ใน main () คุณสร้างสตริงและส่งผ่านไปยัง constructor อินสแตนซ์ของสตริงนี้มีอยู่ภายในตัวสร้างเท่านั้น ภายในตัวสร้างคุณกำหนดให้สมาชิกชี้ไปที่อินสแตนซ์นี้โดยตรง เมื่อเมื่อขอบเขตปล่อยให้ตัวสร้างอินสแตนซ์สตริงถูกทำลายและสมาชิกชี้ไปยังวัตถุสตริงที่ไม่มีอยู่อีกต่อไป การมี Sandbox.member ชี้ไปที่การอ้างอิงนอกขอบเขตจะไม่เก็บอินสแตนซ์ภายนอกเหล่านั้นไว้ในขอบเขต

หากคุณต้องการแก้ไขโปรแกรมของคุณเพื่อแสดงพฤติกรรมที่คุณต้องการให้ทำการเปลี่ยนแปลงต่อไปนี้:

int main()
{
    string temp = string("four");    
    Sandbox sandbox(temp);
    cout << sandbox.member << endl;
    return 0;
}

ตอนนี้อุณหภูมิจะผ่านออกจากขอบเขตที่จุดสิ้นสุดของ main () แทนที่จะเป็นตอนท้ายของ constructor อย่างไรก็ตามนี่คือการปฏิบัติที่ไม่ดี ตัวแปรสมาชิกของคุณไม่ควรอ้างอิงถึงตัวแปรที่มีอยู่นอกอินสแตนซ์ ในทางปฏิบัติคุณไม่มีทางรู้ว่าเมื่อไหร่ที่ตัวแปรนั้นจะออกนอกขอบเขต

สิ่งที่ฉันแนะนำคือการกำหนด Sandbox.member เป็น a const string member;ซึ่งจะเป็นการคัดลอกข้อมูลพารามิเตอร์ชั่วคราวไปยังตัวแปรสมาชิกแทนที่จะกำหนดตัวแปรสมาชิกเป็นพารามิเตอร์ชั่วคราวเอง


ถ้าฉันทำสิ่งนี้const string & temp = string("four"); Sandbox sandbox(temp); cout << sandbox.member << endl;มันจะยังใช้งานได้หรือไม่
Yves

@Thomas const string &temp = string("four");ให้ผลลัพธ์เหมือนconst string temp("four"); กันเว้นแต่ว่าคุณจะใช้เป็นdecltype(temp)พิเศษ
MM

@MM ขอบคุณมากตอนนี้ฉันเข้าใจคำถามนี้โดยสิ้นเชิง
Yves

However, this is bad practice.- ทำไม หากทั้งอุณหภูมิและวัตถุที่มีอยู่ใช้ที่เก็บข้อมูลอัตโนมัติในขอบเขตเดียวกันจะปลอดภัย 100% หรือไม่ และถ้าคุณไม่ทำอย่างนั้นคุณจะทำอย่างไรถ้าสตริงมีขนาดใหญ่เกินไปและแพงเกินไปที่จะคัดลอก?
สูงสุด

2
@max เนื่องจากคลาสไม่บังคับใช้การส่งผ่านชั่วคราวเพื่อให้มีขอบเขตที่ถูกต้อง หมายความว่าวันหนึ่งคุณอาจลืมข้อกำหนดนี้ส่งค่าชั่วคราวที่ไม่ถูกต้องและคอมไพเลอร์จะไม่เตือนคุณ
Alex Che

5

ในทางเทคนิคแล้วโปรแกรมนี้ไม่จำเป็นต้องส่งออกอะไรไปยังเอาต์พุตมาตรฐาน (ซึ่งเป็นสตรีมบัฟเฟอร์ที่เริ่มต้นด้วย)

  • cout << "The answer is: "บิตจะปล่อย"The answer is: "ลงในบัฟเฟอร์ของ stdout

  • จากนั้น<< sandbox.memberบิตจะเป็นผู้จัดหาห้อยอ้างอิงเข้าไปoperator << (ostream &, const std::string &)ซึ่งจะเรียกไม่ได้กำหนดพฤติกรรม

ด้วยเหตุนี้จึงไม่มีสิ่งใดรับประกันได้ว่าจะเกิดขึ้น โปรแกรมอาจทำงานได้ดีหรืออาจล้มเหลวโดยไม่ต้องล้างข้อมูลออก - หมายถึงข้อความ "คำตอบคือ:" จะไม่ปรากฏบนหน้าจอของคุณ


2
เมื่อมี UB พฤติกรรมทั้งหมดของโปรแกรมจะไม่ได้กำหนดไว้ - มันไม่เพียงแค่เริ่มต้นที่จุดใดจุดหนึ่งในการดำเนินการ ดังนั้นเราจึงไม่สามารถพูดได้อย่างแน่นอนว่า"The answer is: "จะเขียนทุกที่
Toby Speight

0

เนื่องจากสตริงชั่วคราวของคุณออกนอกขอบเขตเมื่อตัวสร้างแซนด์บ็อกซ์ส่งคืนและสแต็กที่ครอบครองโดยมันถูกเรียกคืนเพื่อวัตถุประสงค์อื่น

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


7
"ไม่เคย" เป็นคำที่แข็งแกร่งมาก
Fred Larson

17
ไม่เคยเป็นสมาชิกของคลาสเว้นแต่คุณจะต้องอ้างอิงถึงวัตถุ มีหลายกรณีที่คุณต้องเก็บการอ้างอิงไปยังวัตถุอื่น ๆ และไม่คัดลอกสำหรับการอ้างอิงกรณีเหล่านั้นเป็นวิธีที่ชัดเจนกว่าตัวชี้
David Rodríguez - dribeas

0

คุณหมายถึงบางสิ่งที่หายไป ต่อไปนี้จะใช้งานได้

#include <string>
#include <iostream>

class Sandbox
{

public:
    const string member = " "; //default to whatever is the requirement
    Sandbox(const string& n) : member(n) {}//a copy is made

};

int main()
{
    Sandbox sandbox(string("four"));
    std::cout << "The answer is: " << sandbox.member << std::endl;
    return 0;
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.