Tra cứu đối số trực tiếp là gì (hay còn gọi là ADL hay Tra cứu Koenig Trax)?


176

Một số giải thích tốt về tra cứu phụ thuộc đối số là gì? Nhiều người cũng gọi nó là Tra cứu Koenig.

Tốt nhất là tôi muốn biết:

  • Tại sao nó là một điều tốt?
  • Tại sao nó là một điều xấu?
  • Làm thế nào nó hoạt động?




Câu trả lời:


223

Tra cứu Koenig , hoặc Tra cứu phụ thuộc đối số , mô tả cách các tên không đủ tiêu chuẩn được trình biên dịch tìm kiếm trong C ++.

Tiêu chuẩn C ++ 11 § 3.4.2 / 1 nêu:

Khi biểu thức postfix trong lệnh gọi hàm (5.2.2) là id không đủ tiêu chuẩn, các không gian tên khác không được xem xét trong quá trình tra cứu không đủ tiêu chuẩn (3.4.1) có thể được tìm kiếm và trong các không gian tên đó, khai báo hàm bạn bè phạm vi không gian tên ( 11.3) không thể nhìn thấy khác có thể được tìm thấy. Những sửa đổi cho tìm kiếm này phụ thuộc vào các loại đối số (và đối với đối số mẫu khuôn mẫu, không gian tên của đối số mẫu).

Nói một cách đơn giản hơn, Nicolai Josuttis tuyên bố 1 :

Bạn không phải đủ điều kiện không gian tên cho các hàm nếu một hoặc nhiều loại đối số được xác định trong không gian tên của hàm.

Một ví dụ mã đơn giản:

namespace MyNamespace
{
    class MyClass {};
    void doSomething(MyClass);
}

MyNamespace::MyClass obj; // global object


int main()
{
    doSomething(obj); // Works Fine - MyNamespace::doSomething() is called.
}

Trong ví dụ trên có không phải là một using-declaration hay một using-directive nhưng vẫn trình biên dịch một cách chính xác xác định tên không đủ tiêu chuẩn doSomething()như chức năng khai báo trong namespace MyNamespacebằng cách áp dụng tra cứu Koenig .

Làm thế nào nó hoạt động?

Thuật toán yêu cầu trình biên dịch không chỉ nhìn vào phạm vi cục bộ mà cả các không gian tên chứa kiểu của đối số. Do đó, trong đoạn mã trên, trình biên dịch thấy rằng đối tượng obj, là đối số của hàm doSomething(), thuộc về không gian tên MyNamespace. Vì vậy, nó nhìn vào không gian tên đó để xác định vị trí khai báo doSomething().

Lợi thế của tra cứu Koenig là gì?

Như ví dụ mã đơn giản ở trên chứng minh, tra cứu Koenig cung cấp sự tiện lợi và dễ sử dụng cho lập trình viên. Nếu không có tra cứu Koenig, sẽ có một chi phí cho lập trình viên, để liên tục chỉ định các tên đủ điều kiện, hoặc thay vào đó, sử dụng nhiều usingtuyên bố.

Tại sao những lời chỉ trích của tra cứu Koenig?

Quá phụ thuộc vào tra cứu Koenig có thể dẫn đến các vấn đề ngữ nghĩa và đôi khi khiến lập trình viên mất cảnh giác.

Xem xét ví dụ về std::swap, đó là một thuật toán thư viện tiêu chuẩn để trao đổi hai giá trị. Với việc tra cứu Koenig, người ta sẽ phải thận trọng khi sử dụng thuật toán này bởi vì:

std::swap(obj1,obj2);

có thể không hiển thị hành vi tương tự như:

using std::swap;
swap(obj1, obj2);

Với ADL, phiên bản swaphàm nào được gọi sẽ phụ thuộc vào không gian tên của các đối số được truyền cho nó.

Nếu tồn tại một không gian tên Avà nếu A::obj1, A::obj2& A::swap()tồn tại thì ví dụ thứ hai sẽ dẫn đến một cuộc gọi đến A::swap(), đó có thể không phải là điều người dùng muốn.

Hơn nữa, nếu vì một số lý do cả hai A::swap(A::MyClass&, A::MyClass&)std::swap(A::MyClass&, A::MyClass&)được xác định, thì ví dụ đầu tiên sẽ gọi std::swap(A::MyClass&, A::MyClass&)nhưng ví dụ thứ hai sẽ không được biên dịch vì swap(obj1, obj2)sẽ mơ hồ.

Câu đố:

Tại sao lại gọi là Tìm kiếm Koenig?

Bởi vì nó đã được nghĩ ra bởi cựu nhà nghiên cứu và lập trình viên của AT & T và Bell Labs, Andrew Koenig .

Đọc thêm:


1 Định nghĩa về tra cứu Koenig như được định nghĩa trong cuốn sách của Josuttis, Thư viện chuẩn C ++: Hướng dẫn và tham khảo .


11
@AlokSave: +1 cho câu trả lời, nhưng câu đố không đúng. Koenig đã không phát minh ra ADL, vì anh ta thú nhận ở đây :)
huyền thoại2k

20
Ví dụ trong các chỉ trích về Thuật toán Koenig có thể được coi là một "tính năng" của tra cứu Koenig nhiều như một "con". Sử dụng std :: exchange () theo cách như vậy là một thành ngữ phổ biến: Cung cấp 'sử dụng std :: exchange ()' trong trường hợp không cung cấp phiên bản chuyên dụng hơn A :: exchange (). Nếu có sẵn phiên bản chuyên biệt của A :: exchange (), chúng tôi sẽ muốn phiên bản đó được gọi. Điều này cung cấp nhiều tính tổng quát hơn cho lệnh gọi () vì chúng ta có thể tin tưởng vào lệnh gọi để biên dịch và làm việc, nhưng chúng ta cũng có thể tin tưởng phiên bản chuyên dụng hơn sẽ được sử dụng nếu có.
Hội trường Anthony

6
@anthrond Có nhiều hơn về điều này. Với std::swapbạn thực sự phải làm điều đó vì cách thay thế duy nhất sẽ là thêm std::swapchuyên môn rõ ràng về chức năng khuôn mẫu cho Alớp của bạn . Tuy nhiên, nếu Alớp của bạn là một khuôn mẫu, nó sẽ là chuyên môn hóa một phần thay vì chuyên môn hóa rõ ràng. Và chuyên môn hóa một phần của chức năng mẫu không được phép. Thêm quá tải std::swapsẽ là một thay thế nhưng bị cấm rõ ràng (bạn không thể thêm mọi thứ vào stdkhông gian tên). Vì vậy, ADL là cách duy nhất cho std::swap.
Adam Badura

1
Tôi đã dự kiến ​​sẽ thấy một đề cập đến các nhà khai thác quá tải theo "lợi thế của tra cứu koenig". ví dụ với std::swap()một chút ngược. Tôi hy vọng vấn đề sẽ xảy ra khi std::swap()được chọn thay vì quá tải cụ thể cho loại , A::swap(). Ví dụ với std::swap(A::MyClass&, A::MyClass&)có vẻ sai lệch. vì stdsẽ không bao giờ có tình trạng quá tải cụ thể cho loại người dùng, tôi không nghĩ đó là một ví dụ tuyệt vời.
Arvid

1
@ksamaras ... Và? Tất cả chúng ta có thể thấy rằng chức năng không bao giờ được xác định. Thông báo lỗi của bạn chứng minh rằng nó thực sự hoạt động, bởi vì nó đang tìm kiếm MyNamespace::doSomething, không chỉ ::doSomething.
Vụ kiện của Quỹ Monica

69

Trong Tra cứu Koenig, nếu một hàm được gọi mà không chỉ định không gian tên của nó, thì tên của một hàm cũng được tìm kiếm trong (các) không gian tên trong đó loại đối số được xác định. Đó là lý do tại sao nó còn được gọi là Tra cứu tên phụ thuộc đối số , nói ngắn gọn là ADL .

Đó là vì Tra cứu Koenig, chúng ta có thể viết điều này:

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

Nếu không, chúng tôi sẽ phải viết:

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

mà thực sự là quá nhiều gõ và mã trông thực sự xấu xí!

Nói cách khác, trong trường hợp không có Tra cứu Koenig, ngay cả một chương trình Hello World cũng có vẻ phức tạp.


12
Ví dụ thuyết phục.
Hội trường Anthony

10
@AdamBadura: Xin lưu ý rằng đó std::coutlà một đối số cho hàm, đủ để kích hoạt ADL. Bạn có nhận thấy điều đó?
Nawaz

1
@meet: Câu hỏi của bạn cần một câu trả lời dài không thể cung cấp trong không gian này. Vì vậy, tôi chỉ có thể khuyên bạn đọc về các chủ đề như: 1) chữ ký của ostream<<(như trong những gì nó lấy làm đối số và những gì nó trả về). 2) Tên đủ điều kiện (như std::vectorhoặc std::operator<<). 3) Một nghiên cứu chi tiết hơn về Tra cứu phụ thuộc đối số.
Nawaz

2
@WorldSEnder: Vâng, bạn đúng. Hàm có thể lấy std::endllàm đối số, thực sự là hàm thành viên. Dù sao, nếu tôi sử dụng "\n"thay vì std::endl, thì câu trả lời của tôi là chính xác. Cảm ơn các bình luận.
Nawaz

2
@Destructor: Bởi vì một lệnh gọi hàm dưới dạng f(a,b)gọi hàm miễn phí . Vì vậy, trong trường hợp std::operator<<(std::cout, std::endl);, không có hàm miễn phí nào dùng std::endllàm đối số thứ hai. Đây là hàm thành viên dùng std::endllàm đối số và bạn phải viết std::cout.operator<<(std::endl);. và vì có một hàm miễn phí dùng char const*làm đối số thứ hai, nên "\n"hoạt động; '\n'cũng sẽ làm việc
Nawaz

30

Có lẽ tốt nhất là bắt đầu với lý do tại sao, và chỉ sau đó đi đến làm thế nào.

Khi không gian tên được giới thiệu, ý tưởng là có mọi thứ được xác định trong không gian tên, để các thư viện riêng biệt không can thiệp lẫn nhau. Tuy nhiên, điều đó đã giới thiệu một vấn đề với các nhà khai thác. Hãy xem ví dụ ở đoạn mã sau:

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
  ???
}

Tất nhiên bạn có thể đã viết N::operator++(x), nhưng điều đó sẽ đánh bại toàn bộ điểm quá tải của nhà điều hành. Do đó, một giải pháp đã được tìm thấy cho phép trình biên dịch tìm thấy operator++(X&)mặc dù thực tế là nó không nằm trong phạm vi. Mặt khác, nó vẫn không tìm thấy một operator++định nghĩa khác trong không gian tên khác, không liên quan có thể khiến cuộc gọi trở nên mơ hồ (trong ví dụ đơn giản này, bạn sẽ không nhận được sự mơ hồ, nhưng trong các ví dụ phức tạp hơn, bạn có thể). Giải pháp là Tra cứu phụ thuộc đối số (ADL), được gọi theo cách đó vì việc tra cứu phụ thuộc vào đối số (chính xác hơn là vào loại đối số). Vì sơ đồ được phát minh bởi Andrew R. Koenig, nó cũng thường được gọi là tra cứu Koenig.

Thủ thuật là đối với các lệnh gọi hàm, ngoài tra cứu tên thông thường (tìm tên trong phạm vi tại điểm sử dụng), đã thực hiện tra cứu thứ hai trong phạm vi của các loại đối số được cung cấp cho hàm. Vì vậy, trong ví dụ trên, nếu bạn viết x++trong chính, nó sẽ tìm kiếm operator++không chỉ ở phạm vi toàn cầu, nhưng bổ sung trong phạm vi nơi các loại x, N::Xđược xác định, ví dụ trong namespace N. Và ở đó, nó tìm thấy một kết hợp operator++, và do đó x++chỉ hoạt động. Tuy nhiên, một operator++định nghĩa khác trong không gian tên khác N2sẽ không được tìm thấy. Vì ADL không bị giới hạn ở các không gian tên, bạn cũng có thể sử dụng f(x)thay vì N::f(x)trong main().


Cảm ơn! Không bao giờ thực sự hiểu tại sao nó ở đó!
dùng965369

20

Không phải tất cả mọi thứ về nó là tốt, theo ý kiến ​​của tôi. Mọi người, bao gồm các nhà cung cấp trình biên dịch, đã xúc phạm nó vì hành vi đôi khi không may của nó.

ADL chịu trách nhiệm đại tu chính cho vòng lặp for trong phạm vi C ++ 11. Để hiểu tại sao đôi khi ADL có thể có các hiệu ứng ngoài ý muốn, hãy xem xét rằng không chỉ các không gian tên nơi các đối số được xác định được xem xét, mà cả các đối số của các đối số khuôn mẫu của các đối số, các loại tham số của các loại hàm / loại con trỏ của các loại con trỏ của các đối số đó , v.v.

Một ví dụ sử dụng boost

std::vector<boost::shared_ptr<int>> v;
auto x = begin(v);

Điều này dẫn đến sự mơ hồ nếu người dùng sử dụng thư viện boost.range, bởi vì cả hai std::beginđược tìm thấy (bởi ADL sử dụng std::vector) và boost::beginđược tìm thấy (bởi ADL sử dụng boost::shared_ptr).


Tôi đã luôn tự hỏi có lợi ích gì khi xem xét các đối số mẫu ở vị trí đầu tiên.
Dennis Zickefoose

Có công bằng không khi nói rằng ADL chỉ được khuyến nghị cho các nhà khai thác và tốt hơn là viết các không gian tên rõ ràng cho các chức năng khác?
balki

Nó cũng xem xét các không gian tên của các lớp đối số cơ sở? (điều đó sẽ là điên rồ nếu nó, tất nhiên).
Alex B

3
làm thế nào để khắc phục? sử dụng std :: bắt đầu?
paulm

2
@paulm Có, std::beginxóa sự mơ hồ không gian tên.
Nikos
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.