ADL的陷阱是什么?

前段时间我读了一篇文章,解释了参数依赖查找的几个陷阱,但我再也找不到了.这是关于获得对你不应该访问的东西的访问权或类似的东西.所以我想我会在这里问:ADL 的陷阱是什么?

Some time ago I read an article that explained several pitfalls of argument dependent lookup, but I cannot find it anymore. It was about gaining access to things that you should not have access to or something like that. So I thought I'd ask here: what are the pitfalls of ADL?

推荐答案

依赖于参数的查找存在很大的问题.例如,考虑以下实用程序:

There is a huge problem with argument-dependent lookup. Consider, for example, the following utility:

#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);
    }
}

这很简单,对吧?我们可以调用 print_n() 并将任何对象传递给它,它会调用 print 来打印对象 n 次.

It's simple enough, right? We can call print_n() and pass it any object and it will call print to print the object n times.

其实,如果只看这段代码,我们完全不知道print_n会调用什么函数.它可能是这里给出的 print 函数模板,但也可能不是.为什么?依赖于参数的查找.

Actually, it turns out that if we only look at this code, we have absolutely no idea what function will be called by print_n. It might be the print function template given here, but it might not be. Why? Argument-dependent lookup.

例如,假设您编写了一个类来表示独角兽.出于某种原因,您还定义了一个名为 print 的函数(多么巧合!),它只是通过写入一个取消引用的空指针而导致程序崩溃(谁知道您为什么这样做;那不是重要):

As an example, let's say you have written a class to represent a unicorn. For some reason, you've also defined a function named print (what a coincidence!) that just causes the program to crash by writing to a dereferenced null pointer (who knows why you did this; that's not important):

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

接下来,您编写一个小程序,创建一个独角兽并打印四次:

Next, you write a little program that creates a unicorn and prints it four times:

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

你编译这个程序,运行它,然后……它崩溃了.什么?!不可能,"你说:我只是调用了 print_n,它调用了 print 函数来打印四次独角兽!"是的,确实如此,但它没有调用您期望它调用的 print 函数.它被称为 my_stuff::print.

You compile this program, run it, and... it crashes. "What?! No way," you say: "I just called print_n, which calls the print function to print the unicorn four times!" Yes, that's true, but it hasn't called the print function you expected it to call. It's called my_stuff::print.

为什么选择my_stuff::print?在名称查找期间,编译器发现调用 print 的参数是 unicorn 类型,这是在命名空间 my_stuff<中声明的类类型/代码>.

Why is my_stuff::print selected? During name lookup, the compiler sees that the argument to the call to print is of type unicorn, which is a class type that is declared in the namespace my_stuff.

由于依赖于参数的查找,编译器在搜索名为 print 的候选函数时包含此命名空间.它找到 my_stuff::print,然后在重载决议期间将其选为最佳可行候选者:调用任一候选 print 函数和非模板函数都不需要转换首选函数模板,因此非模板函数 my_stuff::print 是最佳匹配.

Because of argument-dependent lookup, the compiler includes this namespace in its search for candidate functions named print. It finds my_stuff::print, which is then selected as the best viable candidate during overload resolution: no conversion is required to call either of the candidate print functions and nontemplate functions are preferred to function templates, so the nontemplate function my_stuff::print is the best match.

(如果您不相信这一点,您可以按原样编译此问题中的代码并查看 ADL 的实际效果.)

(If you don't believe this, you can compile the code in this question as-is and see ADL in action.)

是的,依赖于参数的查找是 C++ 的一个重要特性.它本质上是实现某些语言特性的所需行为,如重载运算符(考虑流库).也就是说,它也非常非常有缺陷,可能会导致非常丑陋的问题.有几个建议来解决依赖于参数的查找,但没有一个被 C++ 标准委员会接受.

Yes, argument-dependent lookup is an important feature of C++. It is essentially required to achieve the desired behavior of some language features like overloaded operators (consider the streams library). That said, it's also very, very flawed and can lead to really ugly problems. There have been several proposals to fix argument-dependent lookup, but none of them have been accepted by the C++ standards committee.

相关文章