"การค้นหาอาร์กิวเมนต์ขึ้นอยู่กับ" (aka ADL หรือ "การค้นหานิก") คืออะไร


176

คำอธิบายที่ดีเกี่ยวกับการค้นหาที่ขึ้นอยู่กับการโต้แย้งคืออะไร? หลายคนก็เรียกมันว่า Koenig Lookup เช่นกัน

โดยเฉพาะอย่างยิ่งฉันต้องการทราบ:

  • ทำไมเป็นสิ่งที่ดี
  • ทำไมมันเป็นสิ่งที่ไม่ดี?
  • มันทำงานยังไง?



9
มันเป็นสิ่งที่ดีเพราะมิฉะนั้นstd::cout << "Hello world";จะไม่รวบรวม
sehe

คำตอบ:


223

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)จะคลุมเครือ

Trivia:

เหตุใดจึงเรียกว่า "การค้นหานิก"

เพราะมันถูกคิดค้นโดยอดีต AT & T และเบลล์แล็บวิจัยและโปรแกรมเมอร์, แอนดรูนิก

อ่านเพิ่มเติม:


1ความหมายของการค้นหานิกเป็นที่กำหนดไว้ใน Josuttis หนังสือ, c ++ มาตรฐานห้องสมุด: การสอนและการอ้างอิง


11
@AlokSave: +1 สำหรับคำตอบ แต่เรื่องไม่สำคัญไม่ถูกต้อง นิกไม่ได้ประดิษฐ์ ADL ในขณะที่เขาสารภาพที่นี่ :)
ตำนาน 2

20
ตัวอย่างในการวิพากษ์วิจารณ์ของ Koenig Algorithm นั้นถือได้ว่าเป็น "คุณสมบัติ" ของ Koenig lookup มากเท่ากับ "con" การใช้ std :: swap () ในลักษณะนี้เป็นสำนวนทั่วไป: ระบุ 'using std :: swap ()' ในกรณีที่ไม่มีรุ่นพิเศษ :: A :: swap () ถ้าเป็นรุ่นพิเศษของ :: แลกเปลี่ยน (บริการ) เราจะ normall ต้องการที่หนึ่งจะเรียกว่า สิ่งนี้จะช่วยให้มีการเรียกสลับ () มากขึ้นเนื่องจากเราสามารถเชื่อถือการเรียกเพื่อคอมไพล์และทำงานได้ แต่เรายังสามารถเชื่อถือรุ่นพิเศษที่จะใช้หากมี
Anthony Hall

6
@anthrond มีอีกมากในเรื่องนี้ กับstd::swapคุณจริงต้องทำตั้งแต่ทางเลือกเดียวที่จะเพิ่มstd::swapฟังก์ชั่นแม่แบบเชี่ยวชาญอย่างชัดเจนสำหรับAระดับ แต่ถ้าAคลาสของคุณเป็นเทมเพลตมันก็จะเป็นความเชี่ยวชาญเฉพาะบางส่วนมากกว่าความเชี่ยวชาญที่ชัดเจน และไม่อนุญาตให้ใช้ฟังก์ชันเทมเพลตบางส่วน การเพิ่มเกินพิกัดstd::swapจะเป็นทางเลือก แต่ห้ามอย่างชัดเจน (คุณไม่สามารถเพิ่มสิ่งต่าง ๆ ในstdเนมสเปซ) ดังนั้น ADL เป็นเพียงstd::swapวิธีการที่
Adam Badura

1
ฉันคาดว่าจะเห็นการกล่าวถึงผู้ประกอบการที่ทำงานหนักเกินไปภายใต้ "ประโยชน์ของการค้นหา koenig" ตัวอย่างที่std::swap()ดูเหมือนจะถอยหลังไปเล็กน้อย ฉันคาดหวังว่าปัญหาจะเกิดขึ้นเมื่อstd::swap()ถูกเลือกมากกว่าการโอเวอร์โหลดเฉพาะสำหรับประเภท, A::swap(). ตัวอย่างที่std::swap(A::MyClass&, A::MyClass&)ดูเหมือนจะทำให้เข้าใจผิด เนื่องจากstdจะไม่มีโอเวอร์โหลดเฉพาะสำหรับประเภทผู้ใช้ฉันไม่คิดว่ามันเป็นตัวอย่างที่ดี
Arvid

1
@gsamaras ... และ? เราทุกคนจะเห็นว่าฟังก์ชั่นไม่เคยถูกกำหนด ข้อผิดพลาดของคุณพิสูจน์ให้เห็นว่ามันทำงานได้จริงเพราะมันกำลังมองหาไม่เพียงMyNamespace::doSomething ::doSomething
คดีฟ้องร้องกองทุนโมนิก้า

69

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

เป็นเพราะการค้นหานิกเราสามารถเขียนสิ่งนี้:

std::cout << "Hello World!" << "\n";

มิฉะนั้นเราจะต้องเขียน:

std::operator<<(std::operator<<(std::cout, "Hello World!"), "\n");

ซึ่งพิมพ์มากเกินไปและรหัสดูน่าเกลียดจริงๆ!

กล่าวอีกนัยหนึ่งในกรณีที่ไม่มี Koenig Lookup แม้แต่โปรแกรมHello World ก็ดูซับซ้อน


12
ตัวอย่างที่โน้มน้าวใจ
Anthony Hall

10
@ AdamBadura: โปรดทราบว่าstd::coutนี่เป็นอาร์กิวเมนต์หนึ่งของฟังก์ชันซึ่งเพียงพอที่จะเปิดใช้งาน ADL คุณสังเกตเห็นว่า?
นาวาซ

1
@ ตอบสนอง: คำถามของคุณต้องการคำตอบยาว ๆ ซึ่งไม่สามารถระบุได้ในพื้นที่นี้ ดังนั้นฉันสามารถแนะนำให้คุณอ่านในหัวข้อต่าง ๆ เช่น: 1) ลายเซ็นต์ของostream<<(เช่นเดียวกับสิ่งที่ใช้เป็นอาร์กิวเมนต์และสิ่งที่ส่งคืน) 2) ชื่อที่ผ่านการรับรองโดยสมบูรณ์ (เช่นstd::vectorหรือstd::operator<<) 3) การศึกษารายละเอียดเพิ่มเติมของการค้นหาแบบอิงอาร์กิวเมนต์
Nawaz

2
@WorldSEnder: ใช่คุณพูดถูก ฟังก์ชั่นที่สามารถใช้std::endlเป็นอาร์กิวเมนต์เป็นจริงฟังก์ชั่นสมาชิก อย่างไรก็ตามถ้าฉันใช้"\n"แทนstd::endlคำตอบของฉันก็ถูกต้อง ขอบคุณสำหรับความคิดเห็น
Nawaz

2
@Destructor: เนื่องจากการเรียกใช้ฟังก์ชันในรูปแบบของการf(a,b)เรียกใช้ฟังก์ชันฟรี ดังนั้นในกรณีที่std::operator<<(std::cout, std::endl);ไม่มีฟังก์ชั่นอิสระดังกล่าวซึ่งใช้std::endlเป็นอาร์กิวเมนต์ที่สอง มันเป็นหน้าที่ของสมาชิกซึ่งจะใช้เวลาเป็นอาร์กิวเมนต์และการที่คุณจะต้องเขียนstd::endl std::cout.operator<<(std::endl);และเนื่องจากมี ฟังก์ชั่นฟรีซึ่งใช้char const*เป็นอาร์กิวเมนต์ที่สอง"\n"ทำงาน; '\n'ก็จะทำงานเช่นกัน
Nawaz

30

อาจจะเป็นการดีที่สุดที่จะเริ่มต้นด้วยสาเหตุและจากนั้นไปที่วิธีการ

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

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()


ขอบคุณ! ไม่เคยเข้าใจจริงๆว่าทำไมมันถึงอยู่ที่นั่น!
user965369

20

ไม่ใช่ทุกอย่างเกี่ยวกับมันดีในความคิดของฉัน ผู้คนรวมถึงผู้ขายคอมไพเลอร์ได้รับการดูถูกเพราะบางครั้งพฤติกรรมที่โชคร้าย

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)


ฉันสงสัยอยู่เสมอว่าสิ่งที่มีประโยชน์คือการพิจารณาข้อโต้แย้งแม่แบบในสถานที่แรก
Dennis Zickefoose

มันยุติธรรมหรือไม่ที่จะบอกว่า ADL แนะนำสำหรับผู้ปฏิบัติงานเท่านั้นและควรเขียนเนมสเปซอย่างชัดเจนสำหรับฟังก์ชั่นอื่น ๆ
balki

นอกจากนี้ยังพิจารณาเนมสเปซของคลาสพื้นฐานของอาร์กิวเมนต์ด้วยหรือไม่ (มันคงจะบ้าแน่ถ้ามันใช่)
Alex B

3
วิธีแก้ไข ใช้ std :: เริ่มต้นหรือไม่
paulm

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