คำอธิบายที่ดีเกี่ยวกับการค้นหาที่ขึ้นอยู่กับการโต้แย้งคืออะไร? หลายคนก็เรียกมันว่า 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()เป็นของการ MyNamespacenamespace ดังนั้นจึงมีลักษณะที่ว่า 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ล้างความกำกวมในเนมสเปซ