ก่อนอื่น "ref-qualifier สำหรับ * this" เป็นเพียง "งบการตลาด" ประเภทของการ*this
ไม่เปลี่ยนแปลงดูด้านล่างของโพสต์นี้ เป็นวิธีที่ง่ายกว่าที่จะเข้าใจด้วยถ้อยคำนี้
ถัดไปรหัสต่อไปนี้เลือกฟังก์ชั่นที่จะเรียกว่าขึ้นอยู่กับref-qualifierของ "พารามิเตอร์วัตถุ implicit" ของฟังก์ชั่น† :
// t.cpp
#include <iostream>
struct test{
void f() &{ std::cout << "lvalue object\n"; }
void f() &&{ std::cout << "rvalue object\n"; }
};
int main(){
test t;
t.f(); // lvalue
test().f(); // rvalue
}
เอาท์พุท:
$ clang++ -std=c++0x -stdlib=libc++ -Wall -pedantic t.cpp
$ ./a.out
lvalue object
rvalue object
มีการทำสิ่งต่าง ๆ ทั้งหมดเพื่อให้คุณสามารถใช้ประโยชน์จากความจริงเมื่อวัตถุที่เรียกใช้ฟังก์ชันเป็นค่า rvalue (เช่นชื่อชั่วคราวเป็นต้น) ใช้รหัสต่อไปนี้เป็นตัวอย่างเพิ่มเติม:
struct test2{
std::unique_ptr<int[]> heavy_resource;
test2()
: heavy_resource(new int[500]) {}
operator std::unique_ptr<int[]>() const&{
// lvalue object, deep copy
std::unique_ptr<int[]> p(new int[500]);
for(int i=0; i < 500; ++i)
p[i] = heavy_resource[i];
return p;
}
operator std::unique_ptr<int[]>() &&{
// rvalue object
// we are garbage anyways, just move resource
return std::move(heavy_resource);
}
};
นี่อาจเป็นสิ่งที่ถูกประดิษฐ์ขึ้นมา แต่คุณควรเข้าใจ
โปรดทราบว่าคุณสามารถรวมcv-qualifier ( const
และvolatile
) และref-qualifiers ( &
และ&&
)
หมายเหตุ: คำพูดมาตรฐานและคำอธิบายการแก้ปัญหาการโอเวอร์โหลดมากมายหลังจากที่นี่!
†เพื่อให้เข้าใจวิธีการทำงานและทำไมคำตอบของ @Nicol Bolas อย่างน้อยก็ส่วนหนึ่งเราต้องขุดในมาตรฐาน C ++ สักหน่อย (ส่วนที่อธิบายว่าทำไมคำตอบของ @ Nicol ผิดที่ด้านล่างถ้าคุณ สนใจเฉพาะในนั้น)
ฟังก์ชั่นซึ่งจะถูกเรียกว่าจะถูกกำหนดโดยกระบวนการที่เรียกว่าความละเอียดเกินพิกัด กระบวนการนี้ค่อนข้างซับซ้อนดังนั้นเราจะแตะต้องสิ่งที่สำคัญสำหรับเราเท่านั้น
ก่อนอื่นสิ่งสำคัญคือการดูว่าการแก้ไขการโอเวอร์โหลดสำหรับฟังก์ชันสมาชิกทำงานอย่างไร:
§13.3.1 [over.match.funcs]
p2 ชุดของฟังก์ชั่นผู้สมัครสามารถมีทั้งฟังก์ชั่นสมาชิกและไม่ใช่สมาชิกที่จะแก้ไขกับรายการอาร์กิวเมนต์เดียวกัน เพื่อให้การโต้แย้งและพารามิเตอร์รายการที่มีการเทียบเคียงภายในชุดที่แตกต่างกันนี้ฟังก์ชันสมาชิกถือว่ามีพารามิเตอร์พิเศษที่เรียกว่าพารามิเตอร์วัตถุโดยปริยายซึ่งหมายถึงวัตถุที่ฟังก์ชันสมาชิกที่ได้รับการเรียกว่า [ ... ]
p3 ในทำนองเดียวกันเมื่อเหมาะสมบริบทสามารถสร้างรายการอาร์กิวเมนต์ที่มีอาร์กิวเมนต์วัตถุโดยนัยเพื่อแสดงวัตถุที่จะดำเนินการ
ทำไมเราถึงต้องเปรียบเทียบฟังก์ชั่นสมาชิกกับผู้ที่ไม่ได้เป็นสมาชิก สาเหตุที่ทำให้เกิดการบรรทุกเกินพิกัด พิจารณาสิ่งนี้:
struct foo{
foo& operator<<(void*); // implementation unimportant
};
foo& operator<<(foo&, char const*); // implementation unimportant
แน่นอนว่าคุณต้องการให้มีการเรียกใช้ฟังก์ชันฟรีต่อไปนี้ใช่ไหม
char const* s = "free foo!\n";
foo f;
f << s;
นั่นเป็นเหตุผลที่ฟังก์ชั่นสมาชิกและไม่ใช่สมาชิกจะรวมอยู่ในชุดที่เรียกว่าโอเวอร์โหลด เพื่อให้การแก้ปัญหามีความซับซ้อนน้อยลงส่วนที่เป็นตัวหนาของเครื่องหมายคำพูดมาตรฐานจะมีอยู่ นอกจากนี้นี่เป็นบิตที่สำคัญสำหรับเรา (ข้อเดียวกัน):
p4 สำหรับฟังก์ชั่นสมาชิกคงที่ประเภทของพารามิเตอร์วัตถุโดยนัยคือ
โดยที่X
เป็นคลาสที่ฟังก์ชันเป็นสมาชิกและcvคือคุณสมบัติของ CV ในการประกาศฟังก์ชันสมาชิก [ ... ]
p5 ในระหว่างการแก้ปัญหาการโอเวอร์โหลด [... ] [t] เขาระบุพารามิเตอร์ของวัตถุ [... ] ยังคงมีเอกลักษณ์เนื่องจากการแปลงในอาร์กิวเมนต์ที่สอดคล้องกันจะต้องปฏิบัติตามกฎเพิ่มเติมเหล่านี้:
[ ... ]
(บิตสุดท้ายเพียงหมายความว่าคุณไม่สามารถโกงการแก้ปัญหาการโอเวอร์โหลดที่เกิดจากการแปลงโดยนัยของวัตถุที่เรียกใช้ฟังก์ชันสมาชิก (หรือโอเปอเรเตอร์))
ลองมาตัวอย่างแรกที่ด้านบนของโพสต์นี้ หลังจากการแปลงสภาพที่กล่าวมาแล้วชุดโหลดเกินมีลักษณะดังนี้:
void f1(test&); // will only match lvalues, linked to 'void test::f() &'
void f2(test&&); // will only match rvalues, linked to 'void test::f() &&'
จากนั้นรายการอาร์กิวเมนต์ที่มีอาร์กิวเมนต์วัตถุโดยนัยจะจับคู่กับรายการพารามิเตอร์ของทุกฟังก์ชั่นที่มีอยู่ในชุดโหลดเกิน ในกรณีของเรารายการอาร์กิวเมนต์จะมีอาร์กิวเมนต์วัตถุนั้นเท่านั้น มาดูกันว่ามันมีลักษณะอย่างไร:
// first call to 'f' in 'main'
test t;
f1(t); // 't' (lvalue) can match 'test&' (lvalue reference)
// kept in overload-set
f2(t); // 't' not an rvalue, can't match 'test&&' (rvalue reference)
// taken out of overload-set
หากหลังจากทดสอบการโอเวอร์โหลดทั้งหมดในชุดแล้วจะมีเพียงหนึ่งการแก้ไขการโอเวอร์โหลดสำเร็จและฟังก์ชันที่เชื่อมโยงกับโอเวอร์โหลดที่แปลงแล้วจะถูกเรียก การโทรครั้งที่สองไปที่ 'f':
// second call to 'f' in 'main'
f1(test()); // 'test()' not an lvalue, can't match 'test&' (lvalue reference)
// taken out of overload-set
f2(test()); // 'test()' (rvalue) can match 'test&&' (rvalue reference)
// kept in overload-set
อย่างไรก็ตามโปรดทราบว่าหากเราไม่ได้ให้ตัวระบุคุณสมบัติไว้ (และไม่ได้มีการใช้งานฟังก์ชันมากเกินไป) ซึ่งf1
จะตรงกับค่า rvalue (ยัง§13.3.1
):
p5 [... ] สำหรับฟังก์ชั่นสมาชิกที่ไม่คงที่ที่ประกาศโดยไม่มีตัวระบุคุณสมบัติจะใช้กฎเพิ่มเติม
- แม้ว่าพารามิเตอร์วัตถุ implicit ไม่ได้ผ่านการ
const
รับรองคุณสมบัติ rvalue สามารถผูกกับพารามิเตอร์ตราบใดที่ทุกประการอาร์กิวเมนต์สามารถแปลงเป็นชนิดของพารามิเตอร์วัตถุ implicit
struct test{
void f() { std::cout << "lvalue or rvalue object\n"; }
};
int main(){
test t;
t.f(); // OK
test().f(); // OK too
}
ทีนี้ทำไมคำตอบของ @ Nicol ถึงผิดอย่างน้อยส่วนหนึ่ง เขาพูดว่า:
*this
โปรดทราบว่าประกาศนี้การเปลี่ยนแปลงประเภทของ
นั่นเป็นสิ่งที่ผิด*this
มีค่า lvalue เสมอ :
§5.3.1 [expr.unary.op] p1
ตัวดำเนิน*
การunary ดำเนินการทางอ้อม : นิพจน์ที่ใช้นั้นจะเป็นตัวชี้ไปยังชนิดของวัตถุหรือตัวชี้ไปยังชนิดของฟังก์ชันและผลลัพธ์คือ lvalue ที่อ้างถึงวัตถุหรือฟังก์ชันที่จุดแสดงออกนั้น
§9.3.2 [class.this] p1
ในเนื้อความของฟังก์ชันสมาชิกแบบไม่คงที่ (9.3) คำสำคัญthis
คือนิพจน์ prvalue ซึ่งค่าคือที่อยู่ของวัตถุที่เรียกใช้ฟังก์ชัน ประเภทของthis
ในฟังก์ชั่นสมาชิกของชั้นเรียนคือX
X*
[ ... ]