cpp primer 5e, P97.
理解
这是一段很容易被忽略、 但是又非常重要的内容。
In § 2.3.1 (p. 51) we noted that there are two exceptions to the rule that the type of a reference must match the type of the object to which it refers. The first exception is that we can initialize a reference to const from any expression that can be converted (§ 2.1.2, p. 35) to the type of the reference. In particular, we can bind a reference to const to a nonconst object, a literal, or a more general expression:
对于 reference 来说,只能是如下的其中之一:
- type of a reference must match the type of the object to which it refers. 引用类型,必须和被引用的类型一样
- the first exception is that, we can initialize a reference to const from any expression that can be converted to the type of reference
第一个例外: 如果一个表达式能够转为被引用的类型, 那么这个表达式能被用于初始化一个引用 - 第二个例外,这里暂时没提及, 应该是右值引用
也就是:
情况1:
T a;
const T& b = a;
情况2:
const T& c = <expression>; // <expression> is type of T, or can be converted to type of T
其中情况2,当表达式不是类型T、但是可以转换为类型T,我想到这几种常见的:
// double/float -> int
const int& r4 = 3.14;
// const char* -> std::string
const std::string& s2 = "hello";
// non-void pointer -> void*
int* data = (int*) malloc(sizeof(int) * 100);
const void* buf = data;
// lambda -> std::function
const std::function<void(int)>& f2 = [](int m) -> void {
std::cout << "m is " << m << std::endl;
};
f2(233);
// functor -> std::function
struct Wangchuqin
{
void operator()(int k)
{
if (k % 3 == 0)
std::cout << "遮球" << std::endl;
else
std::cout << "小幅遮球" << std::endl;
}
};
Wangchuqin wcq;
const std::function<void(int)>& f3 = wcq;
f3(2025);
实践
// P97
#include <iostream>
#include <string>
#include <functional>
void echo(int n)
{
std::cout << "n is " << n << std::endl;
}
struct Wangchuqin
{
void operator()(int k)
{
if (k % 3 == 0)
std::cout << "遮球" << std::endl;
else
std::cout << "小幅遮球" << std::endl;
}
};
int main()
{
// const reference to int
int i = 42;
const int& r1 = i; // we can bind a const int& to a plain int object
const int& r2 = 42; // 42 is an expression, whose type is int
const int& r3 = r1 * 2; // r1 * 2 is an expression, whose type is int
const int& r4 = 3.14;
const int& r5 = sizeof(int);
// const reference to string
const std::string s1 = "hello";
const std::string& s2 = "hello";
// const reference to std::function
const std::function<void(int)> f1 = echo;
f1(42);
// labmda -> std::function
const std::function<void(int)>& f2 = [](int m) -> void {
std::cout << "m is " << m << std::endl;
};
f2(233);
// functor -> std::function
Wangchuqin wcq;
const std::function<void(int)>& f3 = wcq;
f3(2025);
// const reference to pointer
const void* ptr1 = 0;
const void* ptr2 = &i;
int* data = (int*) malloc(sizeof(int) * 100);
const void* buf = data;
free(data);
return 0;
}
更实际的实践
https://godbolt.org/z/bcrhc77TM
#include <functional>
#include <vector>
#include <algorithm>
#include <iostream>
int main() {
using MyFunc = std::function<void(int)>;
std::vector<const MyFunc *> myFuncs;
auto f = [](int v) {
std::cout << v << "\n";
};
auto pushBack = [&myFuncs](const MyFunc & func) {
myFuncs.emplace_back(&func);
};
pushBack(f);
auto callIt = [val = 1] (const MyFunc* func) {
func->operator()(val);
};
for (auto func : myFuncs) {
callIt(func);
}
}
上述代码会 crash。 如果开启 ASAN 则能发现非法内存访问。为什么呢?
因为 pushBack(f)
这句不简单呢:
f
是 lambda- 从
f
到const MyFunc & func
发生了转换
这个转换,是生成「临时的」 MyFuncs 对象。这个所谓的「临时的」,可以认为是匿名的、编译器生成的。 这样的匿名对象,声明周期是有限的:发生在 pushBack
定义中,参数传递阶段:
auto pushBack = [&myFuncs](const MyFunc & func) { // 这里
而具体的实现中,压入的是临时对象的地址:
myFuncs.emplace_back(&func);
为啥说是临时对象的地址? 因为 引用的本质是 别名(alias); 取引用的地址, 其实,就是取匿名对象的地址。
匿名对象马上就「死了」,你还取地址; 那以后再用这个匿名对象,就是使用「鬼魂」,本质是使用非法内存。 那肯定crash啊。
当然,如果你还是不信,可以让编译器告诉你, lambda 和 std::function 类型是不一样的:
https://godbolt.org/z/PWcPvzYG1
template<typename T>
void show_type() {
#ifdef __GNUC__
std::cout << __PRETTY_FUNCTION__ << "\n";
#elif _MSC_VER
std::cout << __FUNCSIG__ << "\n";
#endif
}
show_type<decltype(f)>();
show_type<MyFunc>();
➜ defense-in-cpp git:(main) ✗ cd 9
➜ 9 git:(main) ✗ g++ bar.cpp -std=c++11
./%
➜ 9 git:(main) ✗ ./a.out
void show_type() [T = (lambda at bar.cpp:21:14)]
void show_type() [T = std::function<void (int)>]