C++ ADL参数依赖查找
自以为作为一个C++老鸟,对C++里面各种概念应该都比较熟悉了,但是今天看书的时候又学到了一个装逼的概念ADL,本着学C++装逼装到底的精神,就把这个概念学习了一番。
ADL 的工作原理
在C++中,ADL 是 Argument-Dependent Lookup 的缩写即参数依赖查找。它是一种在特定情况下用来查找函数或运算符的规则。
ADL 会在调用函数时,除了按照通常的作用域规则查找函数外,还会根据函数参数的命名空间和类类型来查找可能的候选函数。
当你调用一个未限定作用域的函数(例如没有写 namespace::
的函数调用),编译器会:
- 先在调用处的普通作用域中查找函数(即通过标准的名称查找规则)。
- 如果未找到匹配的函数,编译器会根据参数类型的命名空间或类所在的命名空间,继续查找函数。
- 编译器会优先选择参数所在命名空间中的函数,而不是全局命名空间中的同名函数。
#include <iostream>
namespace MyNamespace {
struct MyStruct {};
void print(const MyStruct&) {
std::cout << "MyNamespace::print called\n";
}
}
int main() {
MyNamespace::MyStruct obj;
// 调用未限定作用域的函数
print(obj); // ADL 会查找到 MyNamespace::print
return 0;
}
在上面的例子中,虽然没有显式写出 MyNamespace::print(obj)
,但由于参数 obj
是 MyNamespace::MyStruct
类型,ADL 会将 MyNamespace
纳入函数查找范围,最终找到 MyNamespace::print
。
ADL 与运算符重载
其实在最常见的运算符重载中已经有ADL的使用,只不过之前不知道ADL这个概念而已,看下面例子是不是很熟悉
#include <iostream>
namespace Math {
struct Vector {
int x, y;
};
// 定义一个和全局 operator<< 不冲突的版本
std::ostream& operator<<(std::ostream& os, const Vector& v) {
os << "(" << v.x << ", " << v.y << ")";
return os;
}
}
int main() {
Math::Vector v{3, 4};
// ADL 确保 operator<< 从 Math 命名空间查找到正确的定义
std::cout << v << std::endl;
return 0;
}
std::ostream
重载的 <<
流运算符是定义在标准命名空间 std
中的,上面函数本该调用 std::operator<<(std::cout, v),但是在 Math
命名空间中,用户重载了Math::Vector
的 operator<<
定义,ADL 会自动找到正确的 Math::operator<<
。
运算符重载是 ADL 的一个经典应用场景,因为运算符通常与自定义类型的命名空间相关联,ADL 可以确保运算符能正确地从参数的相关命名空间中找到。
ADL 和 std::swap
std::swap
是 C++ 标准库中的一个函数模板,用于交换两个对象的值。为了支持自定义类型,可以在自定义类型的命名空间中重载 swap
。
#include <algorithm> // std::swap
#include <iostream>
namespace Custom {
struct Widget {
int value;
};
// 定义命名空间范围的自定义 swap
void swap(Widget& lhs, Widget& rhs) {
std::swap(lhs.value, rhs.value); // 使用标准库的 swap 交换内部值
std::cout << "Custom::swap called\n";
}
}
int main() {
Custom::Widget w1{10}, w2{20};
// 调用 std::swap
using std::swap;
swap(w1, w2); // ADL 会查找到 Custom::swap
std::cout << "w1.value = " << w1.value << ", w2.value = " << w2.value << "\n";
return 0;
}
调用 swap(w1, w2)
时,标准库的 std::swap
不适合直接处理 Custom::Widget
。ADL 将根据参数 w1
和 w2
的类型,进入 Custom
命名空间,找到 Custom::swap
。
ADL 和隐藏友元函数
隐藏友元函数是指通过在类中定义友元函数,但将其声明和定义放在类的内部,而不是类的外部。这种方式使得友元函数无法直接在类外部被普通的名称查找规则找到,但它可以通过 ADL 被正确查找到。隐藏友元函数的作用通常是防止全局作用域污染,限制函数的可见性,使得函数仅在需要时通过 ADL 查找到。
#include <iostream>
class MyClass {
public:
MyClass(int value) : value_(value) {}
// 声明一个友元函数
friend std::ostream& operator<<(std::ostream& os, const MyClass& obj) {
os << "MyClass(" << obj.value_ << ")";
return os;
}
private:
int value_;
};
int main() {
MyClass obj(42);
// ADL 会查找到友元函数 operator<<
std::cout << obj << std::endl;
// std::operator<< 不会因为友元函数的定义而被隐藏
std::cout << "Test" << std::endl;
return 0;
}
operator<<
是一个隐藏友元函数,因为它的声明和定义都在MyClass
内部。- 当调用
std::cout << obj
时,ADL 会通过参数obj
的类型(MyClass
)进入MyClass
的定义范围,并找到operator<<
。
总结
上面的一些示例代码其实是日常中比较常见的稀疏平常的代码,但是之前基本上不知道里面还有ADL这一说法,相信大家了解ADL概念了,应该对之前一些稀松平常的代码有更深的理解。