当前位置:  开发笔记 > 编程语言 > 正文

ADL的缺陷是什么?

如何解决《ADL的缺陷是什么?》经验,为你挑选了1个好方法。

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



1> James McNell..:

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

#include 

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

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

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

实际上,事实证明,如果我们只看这个代码,我们完全不知道将调用哪个函数print_n.它可能是print这里给出的函数模板,但可能不是.为什么?依赖于参数的查找.

举个例子,假设你写了一个代表独角兽的类.出于某种原因,你还定义了一个名为print(巧合!)的函数,它只是通过写入一个解除引用的空指针(谁知道你为什么这样做;这并不重要)导致程序崩溃:

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

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

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

你编译这个程序,运行它,然后......它崩溃了."什么?!没办法,"你说:"我刚刚打电话print_n,打电话print给四面八国打印独角兽的功能!" 是的,这是真的,但它没有调用print你期望它调用的函数.它被称为my_stuff::print.

为什么my_stuff::print选择?在名称查找期间,编译器会看到调用的参数print是类型unicorn,这是在命名空间中声明的类类型my_stuff.

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

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

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


我想说这不是一个陷阱,而是一个功能:它允许您通过提供专门针对您的类型的实现来覆盖库行为.如果没有ADL,您将无法修改`print`的行为以适应您的`unicorn`类型.一个广泛使用的应用是`swap`:许多标准算法需要交换值; 你可以提供自己优化的`swpa`版本,它将通过ADL选择.当然,如果你可以在不需要时阻止这种覆盖会更好(就像你没有强制要求你的成员函数是虚拟的那样).
@Chubsdad:这是ADL的一个巨大陷阱.问题是,您可以编写两个完全独立的库,并且在不知道您将遇到问题的情况下意外地遇到此问题.没有多少"细心"可以完全保护你免受这种伤害.
这是ADL的陷阱还是不仔细使用ADL的陷阱?
@MSalters:问题是,混合库的显式语句并不总是如此明确.例如,考虑一下,如果你编写一个名为`merge`的命名空间作用域函数模板,它合并了两个东西,你传递了两个`std :: vector`对象.根据你是否包含``(声明`std :: merge`),你会得到不同的结果.
问题是为什么程序员在调用`:: utility :: print()`时会写`print(x)`?如果我写`print(x)`,那么我*打算*调用ADL以便*找到*正确的重载(也可能在其他命名空间中).如果我不想要ADL,那么我会写`:: utility :: print(x)`.所以我不完全*同意这个答案.它主要是因为缺乏关于ADL的基本*知识.我同意@LucTouraille.:-)
不,两个独立的图书馆永远不会有这个问题.你_have_写一个混合它们的显式语句,如上例所示.即使在那里,你也会看到图书馆不喜欢混合使用:mystuff-print与mystuff-unicorn一起使用.
我认为这是一个避免"使用命名空间"的论据(好吧,除了你自己的命名空间),而不是针对ADL.如果指定命名空间,您的读者和编译器都会更好地理解您.对于print_n中的print调用,您可以指定命名空间或将其作为自定义点.
我认为`print_n()`的文档应该提到它多次调用`print()`(以便用户知道ADL问题),或者`print()`应该包含在`命名空间中detail`.在您给出的代码示例中,`print_n()`在`print()`上以ADL的形式有一个未记录的自定义点.
推荐阅读
爱唱歌的郭少文_
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有