ทำไมการอ้างอิงที่ไม่ใช่สมาชิกไม่สามารถผูกกับวัตถุชั่วคราวได้?


226

ทำไมมันไม่ได้รับอนุญาตให้รับการอ้างอิงที่ไม่ใช่ const ไปยังวัตถุชั่วคราวซึ่งฟังก์ชั่นgetx()ส่งกลับ? เห็นได้ชัดว่านี่เป็นสิ่งต้องห้ามตามมาตรฐาน C ++ แต่ฉันสนใจในวัตถุประสงค์ของข้อ จำกัด ดังกล่าวไม่ใช่การอ้างอิงถึงมาตรฐาน

struct X
{
    X& ref() { return *this; }
};

X getx() { return X();}

void g(X & x) {}    

int f()
{
    const X& x = getx(); // OK
    X& x = getx(); // error
    X& x = getx().ref(); // OK
    g(getx()); //error
    g(getx().ref()); //OK
    return 0;
}
  1. เป็นที่ชัดเจนว่าอายุการใช้งานของวัตถุไม่สามารถเป็นสาเหตุได้เนื่องจากการอ้างอิงคงที่ไปยังวัตถุนั้นไม่ได้ถูกห้ามตามมาตรฐาน C ++
  2. เป็นที่ชัดเจนว่าวัตถุชั่วคราวไม่คงที่ในตัวอย่างด้านบนเนื่องจากอนุญาตให้เรียกใช้ฟังก์ชันที่ไม่คงที่ได้ ตัวอย่างเช่นref()สามารถแก้ไขวัตถุชั่วคราว
  3. นอกจากนี้ref()ให้คุณหลอกคอมไพเลอร์และรับลิงก์ไปยังวัตถุชั่วคราวนี้และช่วยแก้ปัญหาของเรา

นอกจากนี้:

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

X& x = getx().ref(); // OK

4
ฉันไม่เห็นด้วยกับ "อายุการใช้งานของวัตถุไม่สามารถเป็นสาเหตุ" ส่วนหนึ่งเพียงเพราะมันระบุไว้ในมาตรฐานที่กำหนดวัตถุชั่วคราวให้กับการอ้างอิง const ขยายอายุการใช้งานของวัตถุนี้ไปตลอดชีวิตของการอ้างอิง const ไม่มีอะไรจะพูดเกี่ยวกับการอ้างอิงที่ไม่ const แม้ว่า ...
SadSido

3
สิ่งที่เป็นสาเหตุของการที่ "ไม่มีการพูดเกี่ยวกับการอ้างอิงที่ไม่ใช่ const แม้ว่า ... " มันเป็นส่วนหนึ่งของคำถามของฉัน มีความรู้สึกใด ๆ ในเรื่องนี้? อาจเป็นผู้เขียนมาตรฐานเพียงลืมเกี่ยวกับการอ้างอิงที่ไม่ใช่ const และอีกไม่นานเราจะเห็นประเด็นหลักต่อไป
Alexey Malistov

2
GotW # 88: ผู้สมัครรับเลือกตั้ง "คณะที่สำคัญที่สุด" herbalutter.spaces.live.com/blog/cns!2D4327CC297151BB!378.entry
fnieto - Fernando Nieto

4
@Michael: VC ผูกค่ากับการอ้างอิงที่ไม่ใช่ const พวกเขาเรียกคุณสมบัตินี้ แต่จริงๆแล้วมันเป็นข้อผิดพลาด (หมายเหตุว่ามันเป็นไม่ได้เป็นปัญหาเพราะมันเป็นเรื่องไร้เหตุผลโดยเนื้อแท้ แต่เพราะมันถูกตัดออกอย่างชัดเจนเพื่อป้องกันข้อผิดพลาดโง่.)
เอสบีไอ

คำตอบ:


97

จากบทความบล็อก Visual C ++ นี้เกี่ยวกับการอ้างอิง rvalue :

... C ++ ไม่ต้องการให้คุณปรับเปลี่ยนเทมเพลตโดยไม่ตั้งใจ แต่การเรียกฟังก์ชันสมาชิกที่ไม่ใช่สมาชิกโดยตรงบนค่า rvalue ที่แก้ไขได้นั้นชัดเจนดังนั้นจึงอนุญาต ...

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

ถ้าฉันเป็นคุณฉันจะคิดใหม่เกี่ยวกับการออกแบบฟังก์ชั่นของฉัน ทำไม g () ยอมรับการอ้างอิงมันแก้ไขพารามิเตอร์หรือไม่ ถ้าไม่ให้ทำการอ้างอิง const ถ้าใช่ทำไมคุณลองส่งผ่านไปชั่วคราวคุณไม่คิดว่ามันเป็นการชั่วคราวที่คุณกำลังแก้ไขหรือไม่? ทำไม getx () ถึงส่งคืนชั่วคราว หากคุณแบ่งปันสถานการณ์จริงของคุณกับเราและสิ่งที่คุณพยายามทำให้สำเร็จคุณอาจได้รับคำแนะนำที่ดีเกี่ยวกับวิธีการทำ

การขัดกับภาษาและการหลอกลวงคอมไพเลอร์ไม่ค่อยแก้ปัญหา - โดยปกติจะสร้างปัญหา


แก้ไข: ตั้งคำถามในความคิดเห็น: 1) X& x = getx().ref(); // OK when will x die?- ฉันไม่รู้และไม่สนใจเพราะนี่คือสิ่งที่ฉันหมายถึงโดย "ไปกับภาษา" ภาษาบอกว่า "ผู้ตายชั่วคราวในตอนท้ายของคำแถลงเว้นแต่พวกเขาจะผูกพันกับการอ้างอิง const ในกรณีนี้พวกเขาจะตายเมื่อการอ้างอิงออกไปนอกขอบเขต" การใช้กฎนั้นดูเหมือนว่า x นั้นตายไปแล้วในตอนต้นของคำสั่งถัดไปเนื่องจากมันไม่ได้ถูกผูกไว้กับการอ้างอิง const (คอมไพเลอร์ไม่ทราบว่า ref () ส่งคืน) นี่เป็นเพียงการคาดเดา

2) ฉันได้ระบุวัตถุประสงค์ไว้อย่างชัดเจน: คุณไม่ได้รับอนุญาตให้แก้ไขขมับเพราะมันไม่สมเหตุสมผล (เพิกเฉยต่อการอ้างอิงค่า C ++ 0x) คำถาม "ทำไมฉันจึงได้รับอนุญาตให้โทรหาสมาชิกที่ไม่ใช่สมาชิกได้?" เป็นคำตอบที่ดี แต่ฉันไม่มีคำตอบที่ดีไปกว่าคำตอบที่ฉันได้กล่าวไปแล้วข้างต้น

3) ถ้าฉันถูก x เกี่ยวกับการX& x = getx().ref();ตายในตอนท้ายของแถลงการณ์ปัญหาจะชัดเจน

อย่างไรก็ตามตามคำถามและความคิดเห็นของคุณฉันไม่คิดว่าแม้คำตอบพิเศษเหล่านี้จะทำให้คุณพึงพอใจ นี่คือความพยายามครั้งสุดท้าย / บทสรุป: คณะกรรมการ C ++ ตัดสินใจว่ามันไม่เหมาะสมที่จะปรับเปลี่ยนทางชั่วคราวดังนั้นพวกเขาจึงไม่อนุญาตให้รวมการอ้างอิงที่ไม่ใช่แบบคอน อาจเป็นการรวบรวมคอมไพเลอร์หรือประเด็นทางประวัติศาสตร์ที่เกี่ยวข้องด้วยฉันไม่รู้ จากนั้นมีกรณีเฉพาะบางอย่างเกิดขึ้นและมีการตัดสินใจว่าเมื่อเทียบกับอัตราต่อรองทั้งหมดพวกเขาจะยังคงอนุญาตให้มีการแก้ไขโดยตรงผ่านการโทรวิธีที่ไม่ใช่ const แต่นั่นเป็นข้อยกเว้น - โดยทั่วไปคุณไม่ได้รับอนุญาตให้แก้ไขขมับ ใช่ C ++ บ่อยครั้งที่แปลก


2
@sbk: 1) จริงๆแล้ววลีที่ถูกต้องคือ: "... ในตอนท้ายของการแสดงออกเต็ม ... " ฉันเชื่อว่า "นิพจน์แบบเต็ม" หมายถึงสิ่งที่ไม่ใช่นิพจน์ย่อยของนิพจน์อื่น ไม่ว่าจะเป็นเช่นนี้เสมอกับ "จุดสิ้นสุดของคำสั่ง" ฉันไม่แน่ใจ
sbi

5
@sbk: 2) ที่จริงแล้วคุณจะได้รับอนุญาตให้ปรับเปลี่ยน rvalues (ชั่วคราว) มันเป็นสิ่งต้องห้ามสำหรับในตัวชนิด ( intฯลฯ ) (std::string("A")+"B").append("C")แต่จะได้รับอนุญาตสำหรับผู้ใช้กำหนดประเภท:
sbi

6
@sbk: 3) เหตุผลที่ Stroustrup ให้ (ใน D&E) สำหรับการไม่อนุญาตให้มีการเชื่อมโยงของค่า rvalues ​​กับการอ้างอิงที่ไม่ใช่แบบ const คือถ้า Alexey g()แก้ไขวัตถุ (ซึ่งคุณคาดหวังจากการใช้การอ้างอิงแบบ non-const) มันจะแก้ไขวัตถุที่กำลังจะตายดังนั้นจึงไม่มีใครสามารถรับค่าที่ดัดแปลงได้ เขาบอกว่านี่น่าจะเป็นข้อผิดพลาด
sbi

2
@sbk: ฉันขอโทษถ้าฉันทำให้คุณขุ่นเคือง แต่ฉันไม่คิดว่า 2) จะเป็น nitpicking ค่านั้นไม่ได้เป็นค่าคงที่เว้นแต่ว่าคุณจะกำหนดค่าดังกล่าวและคุณสามารถเปลี่ยนค่าได้เว้นแต่จะเป็นค่าในตัว ฉันใช้เวลาสักครู่เพื่อทำความเข้าใจว่าด้วยเช่นตัวอย่างสตริงฉันทำอะไรผิดพลาด (JFTR: ฉันไม่) ดังนั้นฉันจึงมักจะแยกความแตกต่างนี้อย่างจริงจัง
sbi

5
ฉันเห็นด้วยกับ sbi - เรื่องนี้ไม่ได้เป็นnitpicking เลย มันเป็นพื้นฐานของความหมายของการย้ายว่าค่าประเภทคลาสจะถูกเก็บไว้อย่างดีที่สุดไม่ใช่ค่าคงที่
Johannes Schaub - litb

38

ในรหัสของคุณgetx()ส่งคืนวัตถุชั่วคราวที่เรียกว่า "rvalue" คุณสามารถคัดลอกค่า rvalues ​​ไปยังวัตถุ (ตัวแปร aka) หรือผูกไว้กับการอ้างอิง const (ซึ่งจะยืดอายุการใช้งานของพวกเขาจนกระทั่งสิ้นสุดการอ้างอิงของชีวิต) คุณไม่สามารถผูกค่า rvalues ​​กับการอ้างอิงที่ไม่ใช่ const

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

g(getx()); // g() would modify an object without anyone being able to observe

หากคุณต้องการทำสิ่งนี้คุณจะต้องทำสำเนาโลคัลหรือวัตถุก่อนหรือผูกกับการอ้างอิง const:

X x1 = getx();
const X& x2 = getx(); // extend lifetime of temporary to lifetime of const reference

g(x1); // fine
g(x2); // can't bind a const reference to a non-const reference

โปรดทราบว่ามาตรฐาน C ++ ถัดไปจะรวมการอ้างอิง rvalue สิ่งที่คุณรู้ว่าเป็นข้อมูลอ้างอิงจึงกลายเป็น "การอ้างอิง lvalue" คุณจะได้รับอนุญาตให้ผูก rvalues ​​เพื่ออ้างอิง rvalue และคุณสามารถโอเวอร์โหลดฟังก์ชั่นใน "rvalue-ness":

void g(X&);   // #1, takes an ordinary (lvalue) reference
void g(X&&);  // #2, takes an rvalue reference

X x; 
g(x);      // calls #1
g(getx()); // calls #2
g(X());    // calls #2, too

แนวคิดเบื้องหลังการอ้างอิง rvalue คือเนื่องจากวัตถุเหล่านี้กำลังจะตายต่อไปคุณสามารถใช้ประโยชน์จากความรู้นั้นและใช้สิ่งที่เรียกว่า "move semantics" ซึ่งเป็นการเพิ่มประสิทธิภาพบางอย่าง:

class X {
  X(X&& rhs)
    : pimpl( rhs.pimpl ) // steal rhs' data...
  {
    rhs.pimpl = NULL; // ...and leave it empty, but deconstructible
  }

  data* pimpl; // you would use a smart ptr, of course
};


X x(getx()); // x will steal the rvalue's data, leaving the temporary object empty

2
สวัสดีนี่เป็นคำตอบที่ยอดเยี่ยม จำเป็นต้องรู้สิ่งหนึ่งสิ่งที่g(getx())ไม่ทำงานเพราะลายเซ็นของมันคือg(X& x)และget(x)ส่งคืนวัตถุชั่วคราวดังนั้นเราจึงไม่สามารถผูกวัตถุชั่วคราว ( rvalue ) เพื่อการอ้างอิงไม่คงที่ถูกต้องหรือไม่ และในโค้ดชิ้นแรกของคุณฉันคิดว่ามันจะเป็นconst X& x2 = getx();แทนconst X& x1 = getx();..
SexyBeast

1
ขอบคุณที่ชี้ข้อผิดพลาดนี้ในคำตอบของฉัน 5 ปีหลังจากที่ฉันเขียนมัน! :-/ใช่การให้เหตุผลของคุณถูกต้องแม้ว่าจะเป็นไปข้างหลังนิดหน่อย: เราไม่สามารถเชื่อมโยงconstขมับกับการอ้างอิงที่ไม่ใช่(lvalue) และดังนั้นการส่งคืนชั่วคราวโดยgetx()(และไม่get(x)) ไม่สามารถผูกมัดกับการอ้างอิง lvalue ที่เป็นข้อโต้แย้งg()ได้
sbi

อืมม, สิ่งที่คุณหมายถึงgetx()(และไม่ได้get(x)) ?
SexyBeast

เมื่อฉันเขียน"... getx () (และไม่ได้รับ (x)) ... "ฉันหมายความว่าชื่อของฟังก์ชันนั้นgetx()ไม่ใช่get(x)(ตามที่คุณเขียน)
sbi

คำตอบนี้จะรวมคำศัพท์ ค่า rvalue เป็นหมวดหมู่นิพจน์ วัตถุชั่วคราวเป็นวัตถุ ค่า rvalue อาจหรือไม่อาจแทนวัตถุชั่วคราว และวัตถุชั่วคราวอาจหรือไม่อาจแสดงโดย rvalue
MM

16

สิ่งที่คุณกำลังแสดงคืออนุญาตให้ผูกมัดผู้ประกอบการ

 X& x = getx().ref(); // OK

นิพจน์คือ 'getx (). ref ();' และสิ่งนี้จะถูกดำเนินการให้เสร็จก่อนที่จะมอบหมายให้ 'x'

โปรดทราบว่า getx () ไม่ส่งคืนการอ้างอิง แต่เป็นวัตถุที่เกิดขึ้นอย่างสมบูรณ์ในบริบทท้องถิ่น วัตถุนั้นเป็นแบบชั่วคราว แต่ไม่ใช่แบบ const ดังนั้นให้คุณโทรหาวิธีอื่นเพื่อคำนวณค่าหรือมีผลข้างเคียงอื่น ๆ เกิดขึ้น

// It would allow things like this.
getPipeline().procInstr(1).procInstr(2).procInstr(3);

// or more commonly
std::cout << getManiplator() << 5;

ดูตอนท้ายของคำตอบเพื่อดูตัวอย่างที่ดีกว่านี้

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

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

x& = const_cast<x&>(getX());

เหตุผลก็คือการทำเช่นนี้กับการอ้างอิง const ไปยังวัตถุชั่วคราวคือมาตรฐานขยายอายุการใช้งานของชั่วคราวไปจนถึงอายุการใช้งานของการอ้างอิงเพื่อยืดอายุการใช้งานของวัตถุชั่วคราวเกินกว่าสิ้นสุดคำสั่ง

ดังนั้นคำถามเดียวที่เหลืออยู่คือเหตุใดมาตรฐานจึงไม่ต้องการอนุญาตให้มีการอ้างอิงถึงขมับเพื่อยืดอายุของวัตถุนอกเหนือจากจุดสิ้นสุดของข้อความ?

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

คิดถึงสถานการณ์นี้:

int getI() { return 5;}
int x& = getI();

x++; // Note x is an alias to a variable. What variable are you updating.

การยืดอายุขัยของวัตถุชั่วคราวนี้จะทำให้สับสนมาก
ในขณะที่ต่อไปนี้:

int const& y = getI();

จะให้รหัสที่ใช้งานและเข้าใจง่าย

หากคุณต้องการแก้ไขค่าคุณควรคืนค่าให้กับตัวแปร หากคุณพยายามหลีกเลี่ยงค่าใช้จ่ายในการคัดลอก obejct กลับมาจากฟังก์ชั่น (ดูเหมือนว่าวัตถุนั้นจะถูกคัดลอกมาสร้างขึ้นใหม่ (ในทางเทคนิคแล้ว)) จากนั้นไม่ต้องกังวลคอมไพเลอร์ดีมากที่'Return Value Optimization'


3
"คำถามที่เหลืออยู่เพียงอย่างเดียวคือทำไมมาตรฐานไม่ต้องการอนุญาตให้มีการอ้างอิงถึงขมับเพื่อยืดอายุของวัตถุที่นอกเหนือจากจุดสิ้นสุดของคำสั่ง?" อย่างนั้นแหละ! คุณเข้าใจคำถามของฉัน แต่ฉันไม่เห็นด้วยกับความคิดเห็นของคุณ คุณพูดว่า "ทำให้คอมไพเลอร์ยากมาก" แต่มันทำเพื่ออ้างอิง const คุณพูดในตัวอย่าง "Note x เป็นนามแฝงของตัวแปรคุณกำลังปรับปรุงตัวแปรอะไร" ไม่มีปัญหา. มีตัวแปรที่ไม่ซ้ำกัน (ชั่วคราว) ต้องเปลี่ยนวัตถุชั่วคราวบางส่วน (เท่ากับ 5)
Alexey Malistov

@Martin: การอ้างอิงที่อ้างอิงไม่เพียงไม่เป็นระเบียบ พวกเขาสามารถนำไปสู่ข้อผิดพลาดร้ายแรงเมื่อเข้าถึงในภายหลังในวิธี
mmmmmmmm

1
@Alexey: โปรดทราบว่าความจริงที่ว่าการเชื่อมโยงไปยังการอ้างอิง const ช่วยเพิ่มอายุการใช้งานของชั่วคราวเป็นข้อยกเว้นที่ถูกเพิ่มเข้ามาอย่างจงใจ (TTBOMK เพื่อให้สามารถปรับแต่งได้เอง) ไม่มีข้อยกเว้นที่เพิ่มเข้ามาสำหรับการอ้างอิงที่ไม่ใช่ const เนื่องจากการผูกชั่วคราวเป็นการอ้างอิงที่ไม่ใช่ const นั้นมักจะเป็นข้อผิดพลาดของโปรแกรมเมอร์
sbi

1
@alexy: การอ้างอิงถึงตัวแปรที่มองไม่เห็น! ไม่ได้เป็นสัญชาตญาณ
Martin York

const_cast<x&>(getX());ไม่สมเหตุสมผล
curiousguy

10

ทำไมถูกกล่าวถึงในคำถามที่ถามบ่อยเกี่ยวกับC ++ (ตัวอักษรตัวหนา ):

ใน C ++ การอ้างอิงที่ไม่ใช่แบบ const สามารถผูกกับ lvalues ​​และการอ้างอิง const สามารถผูกกับ lvalues ​​หรือ rvalues ​​ได้ แต่ไม่มีอะไรที่สามารถผูกกับ rvalue ที่ไม่ใช่ const ที่ให้กับประชาชนป้องกันจากการเปลี่ยนค่านิยมของชั่วคราวที่จะถูกทำลายก่อนค่าใหม่ของพวกเขาสามารถนำมาใช้ ตัวอย่างเช่น:

void incr(int& a) { ++a; }
int i = 0;
incr(i);    // i becomes 1
incr(0);    // error: 0 is not an lvalue

หากอนุญาตให้ incr (0) ทั้งชั่วคราวที่ไม่มีใครเคยเห็นจะเพิ่มขึ้นหรือ - ยิ่งแย่ลง - ค่า 0 จะกลายเป็น 1 หลังดูโง่ แต่จริงๆแล้วมีข้อผิดพลาดเช่นนั้นในคอมไพเลอร์ Fortran ต้นที่ตั้งค่า กันตำแหน่งหน่วยความจำเพื่อเก็บค่า 0


คงจะตลกถ้าเห็นหน้าของโปรแกรมเมอร์ที่ถูกกัดโดย Fortran ว่า "zero bug"! x * 0 ให้x? อะไร? อะไร??
John D

อาร์กิวเมนต์สุดท้ายนั้นอ่อนแอเป็นพิเศษ ไม่มีผู้รวบรวมที่กล่าวถึงจะเปลี่ยนค่าเป็น 0 ถึง 1 หรือตีความincr(0);ในลักษณะดังกล่าว เห็นได้ชัดว่าถ้าสิ่งนี้ได้รับอนุญาตมันจะถูกตีความว่าเป็นการสร้างจำนวนเต็มชั่วคราวและส่งผ่านไปยังincr()
user3204459

6

ประเด็นหลักคือ

g(getx()); //error

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

const X& x = getx(); // OK

ถูกต้องเพราะบางครั้งคุณจำเป็นต้องนำผลลัพธ์การแสดงออกมาใช้ใหม่และค่อนข้างชัดเจนว่าคุณกำลังจัดการกับวัตถุชั่วคราว

อย่างไรก็ตามมันเป็นไปไม่ได้ที่จะทำให้

X& x = getx(); // error

ถูกต้องโดยไม่ทำให้g(getx())ถูกต้องซึ่งเป็นสิ่งที่นักออกแบบภาษาพยายามหลีกเลี่ยงตั้งแต่แรก

g(getx().ref()); //OK

ถูกต้องเพราะวิธีการเพียงรู้เกี่ยวกับ const-ness ของthisพวกเขาไม่ทราบว่าพวกเขาจะถูกเรียกใน lvalue หรือ rvalue

เช่นเคยใน C ++ คุณมีวิธีแก้ปัญหาสำหรับกฎนี้ แต่คุณต้องส่งสัญญาณคอมไพเลอร์ที่คุณรู้ว่าสิ่งที่คุณทำโดยชัดเจน:

g(const_cast<x&>(getX()));

5

ดูเหมือนว่าคำถามเดิมว่าทำไมไม่อนุญาตให้ตอบคำถามนี้อย่างชัดเจน: "เพราะเป็นไปได้มากว่าจะเกิดข้อผิดพลาด"

FWIW ฉันคิดว่าฉันจะแสดงให้เห็นว่ามันสามารถทำได้แม้ว่าฉันจะไม่คิดว่ามันเป็นเทคนิคที่ดี

เหตุผลที่ฉันต้องการส่งชั่วคราวไปยังวิธีการอ้างอิงที่ไม่ใช่ const คือจงใจทิ้งค่าที่ส่งคืนโดยการอ้างอิงที่วิธีการโทรไม่สนใจ บางสิ่งเช่นนี้

// Assuming: void Person::GetNameAndAddr(std::string &name, std::string &addr);
string name;
person.GetNameAndAddr(name, string()); // don't care about addr

ดังที่อธิบายไว้ในคำตอบก่อนหน้าซึ่งไม่ได้รวบรวม แต่การคอมไพล์และทำงานอย่างถูกต้อง (กับคอมไพเลอร์ของฉัน):

person.GetNameAndAddr(name,
    const_cast<string &>(static_cast<const string &>(string())));

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

string name;
string unused;
person.GetNameAndAddr(name, unused); // don't care about addr

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

string name;
{
    string unused;
    person.GetNameAndAddr(name, unused); // don't care about addr
}

- คริส


4

ทำไมคุณเคยต้องการX& x = getx();? เพียงใช้X x = getx();และพึ่งพา RVO


3
เป็นฉันต้องการโทรg(getx())มากกว่าg(getx().ref())
Alexey Malistov

4
@ Alexey นั่นไม่ใช่เหตุผลที่แท้จริง หากคุณทำเช่นนั้นแสดงว่าคุณมีข้อผิดพลาดเชิงตรรกะอย่างใดอย่างหนึ่งเพราะgกำลังจะแก้ไขสิ่งที่คุณไม่สามารถรับมือได้อีกต่อไป
Johannes Schaub - litb

3
@ JohannesSchaub-litb บางทีเขาอาจไม่สนใจ
curiousguy

" พึ่งพา RVO " ยกเว้นจะไม่เรียกว่า "RVO"
curiousguy

2
@curtguy: นั่นเป็นคำที่ยอมรับได้มากสำหรับมัน ไม่มีอะไรผิดปกติที่อ้างถึงว่าเป็น "RVO"
ลูกสุนัข

4

วิธีแก้ปัญหาชั่วร้ายเกี่ยวข้องกับคำหลัก 'ไม่แน่นอน' ที่จริงแล้วความชั่วร้ายนั้นถูกทิ้งไว้เป็นแบบฝึกหัดสำหรับผู้อ่าน หรือดูที่นี่: http://www.ddj.com/cpp/184403758


3

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

อ้างอิงใด ๆ ผูกพันโดยตรงกับชั่วคราวจะยืดอายุของมัน [12.2.5] ในทางกลับกันการอ้างอิงที่เริ่มต้นด้วยการอ้างอิงอื่นจะไม่ (แม้ว่าในที่สุดมันก็ชั่วคราวเดียวกัน) นั่นสมเหตุสมผลแล้ว (ผู้แปลไม่ทราบว่าการอ้างอิงนั้นหมายถึงอะไร)

แต่ความคิดทั้งหมดนี้ทำให้สับสนอย่างมาก เช่นconst X &x = X();จะทำให้ชั่วคราวนานตราบเท่าที่การxอ้างอิง แต่const X &x = X().ref();จะไม่ (ใครจะรู้ว่าสิ่งที่ref()ส่งคืนจริง) ในกรณีหลังตัวทำลายสำหรับการXเรียกในตอนท้ายของบรรทัดนี้ (สิ่งนี้สามารถสังเกตได้ด้วยตัวทำลายที่ไม่สำคัญ)

ดังนั้นดูเหมือนว่าโดยทั่วไปทำให้เกิดความสับสนและอันตราย (ทำไมทำให้กฎระเบียบเกี่ยวกับอายุการใช้งานของวัตถุมีความซับซ้อน) แต่สันนิษฐานว่ามีความต้องการอย่างน้อยสำหรับการอ้างอิง const ดังนั้นมาตรฐานจึงตั้งค่าพฤติกรรมนี้สำหรับพวกเขา

[จากความคิดเห็นsbi ]: โปรดทราบว่าข้อเท็จจริงที่ว่าการเชื่อมโยงไปยังการอ้างอิง const ช่วยเพิ่มอายุการใช้งานของชั่วคราวเป็นข้อยกเว้นที่ถูกเพิ่มเข้ามาอย่างจงใจ (TTBOMK เพื่อให้สามารถปรับแต่งได้เอง) ไม่มีข้อยกเว้นที่เพิ่มเข้ามาสำหรับการอ้างอิงที่ไม่ใช่ const เพราะการผูกชั่วคราวเป็นการอ้างอิงที่ไม่ใช่ const นั้นมักจะเป็นข้อผิดพลาดของโปรแกรมเมอร์

ทุกขมับยังคงอยู่จนกว่าจะสิ้นสุดของการแสดงออกเต็มรูปแบบ อย่างไรก็ตามในการใช้ประโยชน์จากสิ่งเหล่านี้คุณต้องใช้กลอุบายที่เหมือนมีref()อยู่ ถูกต้องตามกฎหมาย ดูเหมือนจะไม่มีเหตุผลที่ดีที่ห่วงพิเศษจะกระโดดข้ามยกเว้นเพื่อเตือนโปรแกรมเมอร์ว่ามีสิ่งผิดปกติเกิดขึ้น (กล่าวคือพารามิเตอร์อ้างอิงซึ่งการแก้ไขจะหายไปอย่างรวดเร็ว)

[ ความคิดเห็นsbiอื่น] เหตุผลที่ Stroustrup ให้ (ใน D & E) สำหรับการไม่อนุญาตให้มีการเชื่อมโยงของค่า rvalues ​​กับการอ้างอิงที่ไม่ใช่ค่าคงที่คือว่าถ้า Alexey g () จะแก้ไขวัตถุ (ซึ่งคุณคาดหวังจากฟังก์ชันที่ไม่ใช่ const การอ้างอิง) มันจะแก้ไขวัตถุที่กำลังจะตายดังนั้นจึงไม่มีใครสามารถรับค่าที่แก้ไขได้ เขาบอกว่านี่น่าจะเป็นข้อผิดพลาด


2

"เป็นที่ชัดเจนว่าวัตถุชั่วคราวไม่คงที่ในตัวอย่างด้านบนเนื่องจากอนุญาตให้เรียกใช้ฟังก์ชันที่ไม่คงที่ตัวอย่างเช่น ref () สามารถแก้ไขวัตถุชั่วคราวได้"

ในตัวอย่างของคุณ getX () ไม่ส่งคืน const X ดังนั้นคุณจึงสามารถโทรหา ref () ได้ในลักษณะเดียวกับที่คุณเรียก X () ref.) คุณส่งคืนการอ้างอิงที่ไม่ใช่ const และสามารถเรียกใช้เมธอดที่ไม่ใช่ const สิ่งที่คุณไม่สามารถทำได้คือกำหนดการอ้างอิงให้กับการอ้างอิงที่ไม่ใช่ const

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


1

ฉันมีสถานการณ์ที่ฉันต้องการแบ่งปันที่ฉันหวังว่าฉันสามารถทำสิ่งที่ Alexey ถาม ในปลั๊กอิน Maya C ++ ฉันต้องทำ shenanigan ต่อไปนี้เพื่อรับค่าลงในแอตทริบิวต์ node:

MFnDoubleArrayData myArrayData;
MObject myArrayObj = myArrayData.create(myArray);   
MPlug myPlug = myNode.findPlug(attributeName);
myPlug.setValue(myArrayObj);

นี่เป็นเรื่องน่าเบื่อที่จะเขียนดังนั้นฉันจึงเขียนฟังก์ชันผู้ช่วยดังต่อไปนี้:

MPlug operator | (MFnDependencyNode& node, MObject& attribute){
    MStatus status;
    MPlug returnValue = node.findPlug(attribute, &status);
    return returnValue;
}

void operator << (MPlug& plug, MDoubleArray& doubleArray){
    MStatus status;
    MFnDoubleArrayData doubleArrayData;
    MObject doubleArrayObject = doubleArrayData.create(doubleArray, &status);
    status = plug.setValue(doubleArrayObject);
}

และตอนนี้ฉันสามารถเขียนรหัสได้ตั้งแต่ต้นโพสต์เป็น:

(myNode | attributeName) << myArray;

ปัญหาคือมันไม่ได้รวบรวมนอก Visual C ++ เพราะมันพยายามผูกตัวแปรชั่วคราวที่ส่งคืนจาก | ตัวดำเนินการอ้างอิง MPlug ของตัวดำเนินการ << ฉันต้องการให้เป็นข้อมูลอ้างอิงเนื่องจากรหัสนี้เรียกว่าหลายครั้งและฉันไม่ต้องการให้มีการคัดลอก MPlug มาก ฉันต้องการวัตถุชั่วคราวเท่านั้นที่จะมีชีวิตอยู่จนกระทั่งสิ้นสุดหน้าที่สอง

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

ขอบคุณ

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