คำอธิบายที่ดีเกี่ยวกับการค้นหาที่ขึ้นอยู่กับการโต้แย้งคืออะไร? หลายคนก็เรียกมันว่า Koenig Lookup เช่นกัน
โดยเฉพาะอย่างยิ่งฉันต้องการทราบ:
- ทำไมเป็นสิ่งที่ดี
- ทำไมมันเป็นสิ่งที่ไม่ดี?
- มันทำงานยังไง?
std::cout << "Hello world";
จะไม่รวบรวม
คำอธิบายที่ดีเกี่ยวกับการค้นหาที่ขึ้นอยู่กับการโต้แย้งคืออะไร? หลายคนก็เรียกมันว่า Koenig Lookup เช่นกัน
โดยเฉพาะอย่างยิ่งฉันต้องการทราบ:
std::cout << "Hello world";
จะไม่รวบรวม
คำตอบ:
Koenig LookupหรือArgument Dependent Lookupอธิบายว่าคอมไพเลอร์ค้นหาชื่ออย่างไรใน C ++
มาตรฐาน C ++ 11 § 3.4.2 / 1 สถานะ:
เมื่อ postfix-expression ในการเรียกใช้ฟังก์ชั่น (5.2.2) เป็น unqualified-id, เนมสเปซอื่นที่ไม่ได้พิจารณาในระหว่างการค้นหาแบบไม่มีเงื่อนไขปกติ (3.4.1) อาจถูกค้นหาและในเนมสเปซเหล่านั้น 11.3) ไม่สามารถมองเห็นได้เป็นอย่างอื่น การแก้ไขการค้นหาเหล่านี้ขึ้นอยู่กับชนิดของอาร์กิวเมนต์ (และสำหรับอาร์กิวเมนต์เท็มเพลตเทมเพลต, เนมสเปซของอาร์กิวเมนต์เท็มเพลต)
ในแง่ง่าย Nicolai Josuttis ฯ1 :
คุณไม่จำเป็นต้องกำหนดเนมสเปซสำหรับฟังก์ชั่นหากมีการกำหนดประเภทอาร์กิวเมนต์หนึ่งประเภทขึ้นไปในเนมสเปซของฟังก์ชัน
ตัวอย่างโค้ดแบบง่าย:
namespace MyNamespace
{
class MyClass {};
void doSomething(MyClass);
}
MyNamespace::MyClass obj; // global object
int main()
{
doSomething(obj); // Works Fine - MyNamespace::doSomething() is called.
}
ในตัวอย่างข้างต้นมีทั้งusing
-declaration หรือมิได้using
-directive แต่ยังคงคอมไพเลอร์อย่างถูกต้องระบุชื่อไม่มีเงื่อนไขdoSomething()
เป็นฟังก์ชั่นที่ประกาศใน namespace MyNamespace
โดยใช้การค้นหานิก
อัลกอริทึมบอกให้คอมไพเลอร์ไม่เพียงแค่ดูขอบเขตภายใน แต่ยังมีเนมสเปซที่มีประเภทของอาร์กิวเมนต์ ดังนั้นในรหัสข้างต้นพบว่าคอมไพเลอร์ว่าวัตถุobj
ซึ่งเป็นข้อโต้แย้งของฟังก์ชั่นที่doSomething()
เป็นของการ MyNamespace
namespace ดังนั้นจึงมีลักษณะที่ว่า namespace doSomething()
เพื่อหาประกาศของ
ดังตัวอย่างโค้ดด้านบนแสดงให้เห็นว่าการค้นหานิกให้ความสะดวกสบายและความสะดวกในการใช้งานโปรแกรมเมอร์ หากไม่มีการค้นหานิกจะมีโอเวอร์เฮดบนโปรแกรมเมอร์เพื่อระบุชื่อที่ผ่านการรับรองโดยสมบูรณ์หรือใช้using
-declarations แทนจำนวนมาก
การพึ่งพิงการค้นหานิกมากเกินไปอาจนำไปสู่ปัญหาทางความหมายและทำให้โปรแกรมเมอร์ไม่สามารถป้องกันได้ในบางครั้ง
ลองพิจารณาตัวอย่างstd::swap
ซึ่งเป็นอัลกอริธึมไลบรารีมาตรฐานเพื่อสลับสองค่า ด้วยการค้นหา Koenig เราจะต้องระมัดระวังในการใช้อัลกอริทึมนี้เพราะ:
std::swap(obj1,obj2);
อาจไม่แสดงพฤติกรรมเช่นเดียวกับ:
using std::swap;
swap(obj1, obj2);
ด้วย ADL เวอร์ชันของswap
ฟังก์ชันที่ถูกเรียกจะขึ้นอยู่กับเนมสเปซของอาร์กิวเมนต์ที่ส่งไปให้
ถ้ามีอยู่ namespace A
และถ้าA::obj1
, A::obj2
และA::swap()
มีอยู่แล้วตัวอย่างที่สองจะมีผลในการเรียกร้องให้A::swap()
ซึ่งอาจจะไม่ได้สิ่งที่ผู้ใช้ต้องการ
เพิ่มเติมถ้าด้วยเหตุผลบางอย่างทั้งสองA::swap(A::MyClass&, A::MyClass&)
และstd::swap(A::MyClass&, A::MyClass&)
ถูกกำหนดแล้วตัวอย่างแรกจะเรียกstd::swap(A::MyClass&, A::MyClass&)
แต่ที่สองจะไม่รวบรวมเพราะswap(obj1, obj2)
จะคลุมเครือ
เพราะมันถูกคิดค้นโดยอดีต AT & T และเบลล์แล็บวิจัยและโปรแกรมเมอร์, แอนดรูนิก
มาตรฐาน C ++ 03/11 [basic.lookup.argdep]: 3.4.2 การค้นหาชื่อที่ขึ้นอยู่กับอาร์กิวเมนต์
1ความหมายของการค้นหานิกเป็นที่กำหนดไว้ใน Josuttis หนังสือ, c ++ มาตรฐานห้องสมุด: การสอนและการอ้างอิง
std::swap
คุณจริงต้องทำตั้งแต่ทางเลือกเดียวที่จะเพิ่มstd::swap
ฟังก์ชั่นแม่แบบเชี่ยวชาญอย่างชัดเจนสำหรับA
ระดับ แต่ถ้าA
คลาสของคุณเป็นเทมเพลตมันก็จะเป็นความเชี่ยวชาญเฉพาะบางส่วนมากกว่าความเชี่ยวชาญที่ชัดเจน และไม่อนุญาตให้ใช้ฟังก์ชันเทมเพลตบางส่วน การเพิ่มเกินพิกัดstd::swap
จะเป็นทางเลือก แต่ห้ามอย่างชัดเจน (คุณไม่สามารถเพิ่มสิ่งต่าง ๆ ในstd
เนมสเปซ) ดังนั้น ADL เป็นเพียงstd::swap
วิธีการที่
std::swap()
ดูเหมือนจะถอยหลังไปเล็กน้อย ฉันคาดหวังว่าปัญหาจะเกิดขึ้นเมื่อstd::swap()
ถูกเลือกมากกว่าการโอเวอร์โหลดเฉพาะสำหรับประเภท, A::swap()
. ตัวอย่างที่std::swap(A::MyClass&, A::MyClass&)
ดูเหมือนจะทำให้เข้าใจผิด เนื่องจากstd
จะไม่มีโอเวอร์โหลดเฉพาะสำหรับประเภทผู้ใช้ฉันไม่คิดว่ามันเป็นตัวอย่างที่ดี
MyNamespace::doSomething
::doSomething
ในการค้นหานิกหากฟังก์ชั่นถูกเรียกโดยไม่ต้องระบุ namespace ของมันแล้วชื่อของฟังก์ชั่นยังค้นหาใน namespace (s) ซึ่งประเภทของการโต้แย้งที่กำหนดไว้ นั่นคือเหตุผลที่มันยังเป็นที่รู้จักกันเป็นอาร์กิวเมนต์ขึ้นกับชื่อการค้นหาในระยะสั้นเพียงADL
เป็นเพราะการค้นหานิกเราสามารถเขียนสิ่งนี้:
std::cout << "Hello World!" << "\n";
มิฉะนั้นเราจะต้องเขียน:
std::operator<<(std::operator<<(std::cout, "Hello World!"), "\n");
ซึ่งพิมพ์มากเกินไปและรหัสดูน่าเกลียดจริงๆ!
กล่าวอีกนัยหนึ่งในกรณีที่ไม่มี Koenig Lookup แม้แต่โปรแกรมHello World ก็ดูซับซ้อน
std::cout
นี่เป็นอาร์กิวเมนต์หนึ่งของฟังก์ชันซึ่งเพียงพอที่จะเปิดใช้งาน ADL คุณสังเกตเห็นว่า?
ostream<<
(เช่นเดียวกับสิ่งที่ใช้เป็นอาร์กิวเมนต์และสิ่งที่ส่งคืน) 2) ชื่อที่ผ่านการรับรองโดยสมบูรณ์ (เช่นstd::vector
หรือstd::operator<<
) 3) การศึกษารายละเอียดเพิ่มเติมของการค้นหาแบบอิงอาร์กิวเมนต์
std::endl
เป็นอาร์กิวเมนต์เป็นจริงฟังก์ชั่นสมาชิก อย่างไรก็ตามถ้าฉันใช้"\n"
แทนstd::endl
คำตอบของฉันก็ถูกต้อง ขอบคุณสำหรับความคิดเห็น
f(a,b)
เรียกใช้ฟังก์ชันฟรี ดังนั้นในกรณีที่std::operator<<(std::cout, std::endl);
ไม่มีฟังก์ชั่นอิสระดังกล่าวซึ่งใช้std::endl
เป็นอาร์กิวเมนต์ที่สอง มันเป็นหน้าที่ของสมาชิกซึ่งจะใช้เวลาเป็นอาร์กิวเมนต์และการที่คุณจะต้องเขียนstd::endl
std::cout.operator<<(std::endl);
และเนื่องจากมี ฟังก์ชั่นฟรีซึ่งใช้char const*
เป็นอาร์กิวเมนต์ที่สอง"\n"
ทำงาน; '\n'
ก็จะทำงานเช่นกัน
อาจจะเป็นการดีที่สุดที่จะเริ่มต้นด้วยสาเหตุและจากนั้นไปที่วิธีการ
เมื่อมีการแนะนำเนมสเปซแนวคิดจะต้องมีทุกสิ่งที่กำหนดไว้ในเนมสเปซเพื่อให้ไลบรารีที่แยกจากกันไม่รบกวนซึ่งกันและกัน อย่างไรก็ตามที่แนะนำปัญหากับผู้ประกอบการ ดูตัวอย่างที่รหัสต่อไปนี้:
namespace N
{
class X {};
void f(X);
X& operator++(X&);
}
int main()
{
// define an object of type X
N::X x;
// apply f to it
N::f(x);
// apply operator++ to it
???
}
แน่นอนคุณสามารถเขียนได้N::operator++(x)
แต่นั่นอาจเอาชนะจุดรวมของผู้ปฏิบัติงานมากเกินไป ดังนั้นจึงต้องพบวิธีแก้ปัญหาซึ่งทำให้คอมไพเลอร์สามารถค้นหาได้operator++(X&)
แม้ว่าจะไม่ได้อยู่ในขอบเขตก็ตาม ในทางกลับกันก็ยังไม่ควรหาอีกoperator++
กำหนดไว้ในอีก namespace ที่ไม่เกี่ยวข้องซึ่งอาจทำให้การโทรที่ไม่ชัดเจน (ในตัวอย่างง่ายๆนี้คุณจะไม่ได้รับความคลุมเครือ แต่ในตัวอย่างที่ซับซ้อนมากขึ้นคุณอาจ) วิธีแก้ไขคือการค้นหาการอ้างถึงอาร์กิวเมนต์ (ADL) ที่เรียกว่าด้วยวิธีนี้เนื่องจากการค้นหาขึ้นอยู่กับอาร์กิวเมนต์ (ตรงตามประเภทของอาร์กิวเมนต์) เนื่องจากรูปแบบนี้ถูกคิดค้นโดย Andrew R. Koenig จึงมักถูกเรียกว่าการค้นหา Koenig
เคล็ดลับคือสำหรับการเรียกใช้ฟังก์ชันนอกเหนือจากการค้นหาชื่อปกติ (ซึ่งค้นหาชื่อในขอบเขตที่จุดใช้งาน) จะทำการค้นหาครั้งที่สองในขอบเขตของประเภทของอาร์กิวเมนต์ใด ๆ ที่กำหนดให้กับฟังก์ชัน ดังนั้นในตัวอย่างข้างต้นถ้าคุณเขียนx++
ในหลักจะมองหาoperator++
ไม่เพียง แต่ในขอบเขตทั่วโลก แต่ยังอยู่ในขอบเขตที่ประเภทของx
, ถูกกำหนดไว้เช่นในN::X
namespace N
และที่นั่นก็พบการจับคู่operator++
และดังนั้นจึงใช้x++
งานได้ อย่างไรก็ตามอีกคำoperator++
จำกัดความที่กำหนดไว้ในเนมสเปซอื่นN2
จะไม่พบ ตั้งแต่ ADL ไม่ได้ จำกัด namespaces คุณยังสามารถใช้f(x)
แทนในN::f(x)
main()
ไม่ใช่ทุกอย่างเกี่ยวกับมันดีในความคิดของฉัน ผู้คนรวมถึงผู้ขายคอมไพเลอร์ได้รับการดูถูกเพราะบางครั้งพฤติกรรมที่โชคร้าย
ADL รับผิดชอบการปรับปรุงครั้งใหญ่ของลูปสำหรับช่วงใน C ++ 11 เพื่อให้เข้าใจว่าทำไมบางครั้ง ADL ถึงมีผลกระทบที่ไม่ได้ตั้งใจให้พิจารณาว่าไม่เพียง แต่ namespaces ที่มีการพิจารณาข้อโต้แย้งนั้นยังได้รับการพิจารณา แต่ยังรวมถึงอาร์กิวเมนต์ของอาร์กิวเมนต์เทมเพลตของอาร์กิวเมนต์ และอื่น ๆ และอื่น ๆ
ตัวอย่างการใช้บูสต์
std::vector<boost::shared_ptr<int>> v;
auto x = begin(v);
สิ่งนี้ส่งผลให้เกิดความกำกวมหากผู้ใช้ใช้ไลบรารี boost.range เพราะทั้งคู่std::begin
ถูกพบ (โดย ADL ใช้std::vector
) และboost::begin
ถูกพบ (โดย ADL ใช้boost::shared_ptr
)
std::begin
ล้างความกำกวมในเนมสเปซ