当前位置: 首页 > news >正文

C++: Initialization and References to const 初始化和常引用

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;elsestd::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;elsestd::cout << "小幅遮球" << std::endl;}
};int main()
{// const reference to intint i = 42;const int& r1 = i; // we can bind a const int& to a plain int objectconst int& r2 = 42; // 42 is an expression, whose type is intconst int& r3 = r1 * 2; // r1 * 2 is an expression, whose type is intconst int& r4 = 3.14;const int& r5 = sizeof(int);// const reference to stringconst std::string s1 = "hello";const std::string& s2 = "hello";// const reference to std::functionconst std::function<void(int)> f1 = echo;f1(42);// labmda -> std::functionconst std::function<void(int)>& f2 = [](int m) -> void {std::cout << "m is " << m << std::endl;};f2(233);// functor -> std::functionWangchuqin wcq;const std::function<void(int)>& f3 = wcq;f3(2025);// const reference to pointerconst 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
  • fconst 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_VERstd::cout << __FUNCSIG__ << "\n";
#endif
}show_type<decltype(f)>();
show_type<MyFunc>();
➜  defense-in-cpp git:(main)cd 99 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)>]

相关文章:

  • 数字ic后端设计从入门到精通(含fusion compiler, tcl教学)
  • C语言自定义类型详解一:结构体(内存对齐)
  • 数据结构(六)——红黑树及模拟实现
  • Linux系统编程 day2
  • 4月15日星期二今日早报简报微语报早读
  • Etcd 推荐配置(生产环境)
  • 路由重定向:redirect
  • PowerBI 度量值分组文件夹管理
  • Python之机器学习入门
  • QuickAPI 核心功能解析:Web 化数据库管理工具的革新与实践
  • ubuntu上SSH防止暴力破解帐号密码
  • Linux网络协议之SSH
  • 【AI提示词】业务开发经理
  • 660 中值定理
  • 黑神话悟空像素版 中文单机版
  • GPIO _OUTPUT-NORMAL 模式
  • Ubuntu和Debian 操作系统的同与异
  • Vue 高级技巧深度解析
  • 【星闪模组开发板WS8204SLEBLEModule】星闪数据收发测试
  • 信息系统项目管理师-工具名词解释(下)
  • 西北大学副校长成陕西首富?旗下巨子生物去年净利超20亿,到底持股多少
  • 耗资10亿潮汕豪宅“英之园”将强拆?区政府:非法占用集体土地
  • 观众走入剧院空间,人艺之友一起“再造时光”
  • 城事 | 重庆新增热门打卡地标,首座熊猫主题轨交站亮相
  • 学习时报头版:世界要公道不要霸道
  • 富家罹盗与财富迷思:《西游记》与《蜃楼志》中的强盗案