45

Một số thời gian trước đây tôi đọc một bài báo giải thích một số cạm bẫy của tra cứu phụ thuộc đối số, nhưng tôi không thể tìm thấy nó nữa. Đó là về việc đạt được quyền truy cập vào những thứ mà bạn không nên có quyền truy cập vào hoặc một cái gì đó như thế. Vì vậy, tôi nghĩ rằng tôi muốn hỏi ở đây: những cạm bẫy của ADL là gì?Cạm bẫy của ADL là gì?

Trả lời

63

Có vấn đề lớn với tra cứu phụ thuộc vào đối số. Hãy xem xét, ví dụ: tiện ích sau:

#include <iostream> 

namespace utility 
{ 
    template <typename T> 
    void print(T x) 
    { 
     std::cout << x << std::endl; 
    } 

    template <typename T> 
    void print_n(T x, unsigned n) 
    { 
     for (unsigned i = 0; i < n; ++i) 
      print(x); 
    } 
} 

Đơn giản, phải không? Chúng ta có thể gọi print_n() và vượt qua bất kỳ đối tượng nào và nó sẽ gọi print để in đối tượng n lần.

Thực tế, hóa ra là nếu chúng ta chỉ xem mã này, chúng tôi có hoàn toàn không có ý tưởng chức năng nào sẽ được gọi bởi print_n. Nó có thể là mẫu chức năng print được đưa ra ở đây, nhưng nó có thể không được. Tại sao? Tra cứu phụ thuộc vào đối số.

Ví dụ: giả sử bạn đã viết một lớp để đại diện cho một con kỳ lân. Vì lý do nào đó, bạn cũng đã xác định một hàm có tên là print (thật trùng hợp!) Mà chỉ khiến chương trình bị lỗi bằng cách ghi vào con trỏ null (ai biết tại sao bạn làm điều này; điều đó không quan trọng):

namespace my_stuff 
{ 
    struct unicorn { /* unicorn stuff goes here */ }; 

    std::ostream& operator<<(std::ostream& os, unicorn x) { return os; } 

    // Don't ever call this! It just crashes! I don't know why I wrote it! 
    void print(unicorn) { *(int*)0 = 42; } 
} 

Tiếp theo, bạn viết một chương trình nhỏ mà tạo ra một con kỳ lân và in nó gấp bốn lần:

int main() 
{ 
    my_stuff::unicorn x; 
    utility::print_n(x, 4); 
} 

bạn biên dịch chương trình này, chạy nó, và ... nó bị treo. "Cái gì ?! Không có cách nào," bạn nói: "Tôi chỉ gọi là print_n, gọi hàm print để in lân bốn lần!" Có, đó là sự thật, nhưng nó đã không được gọi là chức năng print bạn mong đợi nó để gọi. Nó được gọi là my_stuff::print.

Tại sao chọn my_stuff::print? Trong quá trình tra cứu tên, trình biên dịch thấy rằng đối số cho cuộc gọi đến print là loại unicorn, là loại lớp được khai báo trong không gian tên my_stuff.

Do tra cứu phụ thuộc vào đối số, trình biên dịch bao gồm không gian tên này trong tìm kiếm của nó cho các hàm ứng viên có tên là print. Nó tìm thấy my_stuff::print, sau đó được chọn là ứng viên khả thi tốt nhất trong quá trình phân giải quá tải: không cần chuyển đổi để gọi một trong hai chức năng print và chức năng nontemplate được ưu tiên cho mẫu chức năng, do đó hàm nontemplate my_stuff::print là kết quả phù hợp nhất.

(Nếu bạn không tin điều này, bạn có thể biên dịch mã trong câu hỏi này như-là và xem ADL trong hành động.)

Vâng, tra cứu luận phụ thuộc là một tính năng quan trọng của C++. Về bản chất, nó được yêu cầu để đạt được hành vi mong muốn của một số tính năng ngôn ngữ như các toán tử quá tải (xem xét thư viện suối). Điều đó nói rằng, nó cũng rất, rất thiếu sót và có thể dẫn đến những vấn đề thực sự xấu xí. Đã có một số đề xuất để sửa chữa tra cứu phụ thuộc vào đối số, nhưng không có đề xuất nào được chấp nhận bởi ủy ban tiêu chuẩn C++.

+0

Tôi có lẽ nên lưu ý rằng ví dụ này được lấy cảm hứng từ một bài thuyết trình về chủ đề mà Bartosz Milewski đã đưa ra; Tôi không có các slide từ bài thuyết trình đó, và nó không hoàn toàn giống nhau, nhưng nó rất gần. –

+6

Đây có phải là một lỗ hổng của ADL hoặc pitfall của việc không sử dụng ADL một cách cẩn thận? – Chubsdad

+13

@Chubsdad: Đó là một lỗ hổng lớn của ADL. Vấn đề là bạn có thể viết hai thư viện hoàn toàn độc lập và vô tình chạy vào vấn đề này mà không có bất kỳ ý tưởng rằng bạn sẽ có vấn đề. Không có số lượng "cẩn thận" hoàn toàn có thể bảo vệ bạn khỏi điều này. –

Các vấn đề liên quan