|
@@ -192,7 +192,7 @@ class Point1 {
|
|
|
|
|
|
|
|
既然知道这个类的第一个属性是一个指针,是否可以通过指针获取到虚函数表,然后获取虚函数表的第一个函数并执行它呢?
|
|
既然知道这个类的第一个属性是一个指针,是否可以通过指针获取到虚函数表,然后获取虚函数表的第一个函数并执行它呢?
|
|
|
|
|
|
|
|
-> **tip**: 下面的例子可能在 MSVC 中运行失败,因为 MSVC 的虚函数表的第一位存储着 tpye_info 也就是类的类型信息
|
|
|
|
|
|
|
+> **tip**: 下面的例子可能在 MSVC 中运行失败,因为 MSVC 的虚函数表的第一位存储着 type_info 也就是类的类型信息
|
|
|
|
|
|
|
|
```cpp
|
|
```cpp
|
|
|
#include <iostream>
|
|
#include <iostream>
|
|
@@ -522,7 +522,7 @@ int main()
|
|
|
- MSVC 存储在类型关联的虚函数表中
|
|
- MSVC 存储在类型关联的虚函数表中
|
|
|
- GCC/Clang 存储在独立的内存区域中
|
|
- GCC/Clang 存储在独立的内存区域中
|
|
|
|
|
|
|
|
-也就是说,如果使用 `MSCV` 运行前面的通过虚函数表直接执行虚函数的例子是不能正常运行的,因为 `MSVC` 的虚函数表存储着额外的信息
|
|
|
|
|
|
|
+也就是说,如果使用 `MSVC` 运行前面的通过虚函数表直接执行虚函数的例子是不能正常运行的,因为 `MSVC` 的虚函数表存储着额外的信息
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -578,6 +578,8 @@ public:
|
|
|
|
|
|
|
|
### 左值右值
|
|
### 左值右值
|
|
|
|
|
|
|
|
|
|
+#### 定义
|
|
|
|
|
+
|
|
|
- 左值:命名对象、可取地址、可赋值
|
|
- 左值:命名对象、可取地址、可赋值
|
|
|
- 基本变量类型、数组、数组元素
|
|
- 基本变量类型、数组、数组元素
|
|
|
- 字符串字面量 `"success"[0]` 和 `&"success"` 可以正常编过
|
|
- 字符串字面量 `"success"[0]` 和 `&"success"` 可以正常编过
|
|
@@ -640,7 +642,81 @@ int main()
|
|
|
}
|
|
}
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-> mscv 编不过,clang 可以
|
|
|
|
|
|
|
+> msvc 编不过,clang 可以
|
|
|
|
|
|
|
|
可以注意以下 `process` 函数,虽然 `process(x+y)` 触发的是右值,但是在函数中,`data` 其实是个左值
|
|
可以注意以下 `process` 函数,虽然 `process(x+y)` 触发的是右值,但是在函数中,`data` 其实是个左值
|
|
|
|
|
|
|
|
|
|
+#### 移动
|
|
|
|
|
+
|
|
|
|
|
+- 左值:有身份,不呢个移动
|
|
|
|
|
+- 纯右值:没身份,可移动
|
|
|
|
|
+- 将亡值:有身份,可以移动
|
|
|
|
|
+
|
|
|
|
|
+当源对象是一个左值,移动左值并不安全,因为左值后续持续存在,可能被引用,虽然可以将左值强制转换为右值,但是需要自负安全
|
|
|
|
|
+
|
|
|
|
|
+当源对象是一个右值,移动很安全
|
|
|
|
|
+
|
|
|
|
|
+```cpp
|
|
|
|
|
+void func(TestCls&& in);
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+上述代码,形参 `in` 到底是右值还是左值?
|
|
|
|
|
+
|
|
|
|
|
+- 对函数调用者来说, `in` 是一个右值引用,要传递优质
|
|
|
|
|
+- 对函数内部来说,`in` 是一个左值引用(可以取地址)
|
|
|
|
|
+
|
|
|
|
|
+```cpp
|
|
|
|
|
+TestCls a1(1, 2);
|
|
|
|
|
+TestCls a2 = a1; // 左值源 拷贝构造
|
|
|
|
|
+a1 = a2; // 左值源 拷贝赋值
|
|
|
|
|
+a1 = std::move(a2); // 移动赋值
|
|
|
|
|
+
|
|
|
|
|
+// a2.process(); // 危险操作
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+> `std::move` 转为右值
|
|
|
|
|
+
|
|
|
|
|
+一般来说,一个对象被 `std::move` 之后,这个对象被认定为无效对象,后续代码中不应再次使用,所以在 `std::move` 之后仍然执行 `a2.process()` 是危险操作,可能导致不确定后果
|
|
|
|
|
+
|
|
|
|
|
+```cpp
|
|
|
|
|
+TestCls GetTestCls()
|
|
|
|
|
+{
|
|
|
|
|
+ TestCls a1(1, 2);
|
|
|
|
|
+ return a1;
|
|
|
|
|
+ // 编译器自动优化为 return std::move(a1);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+TestCls a2 = GetTestCls(); // 不触发任何构造 复制初始化,但可能被优化
|
|
|
|
|
+TestCls a3(GetTestCls()); // 不触发任何构造 直接初始化
|
|
|
|
|
+a2 = GetTestCls(); // 触发移动赋值,此时 a2 已经构造完毕,无法触发优化
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+上述代码,`GetTestCls` 函数返回值是一个临时对象,在接收的时候触发的是移动构造
|
|
|
|
|
+
|
|
|
|
|
+上述代码,`TestCls a3(GetTestCls())` 不会触发任何构造,这是因为 C++ 的返回值优化,编译器优化**直接构造对象到目标内存**,消除临时对象
|
|
|
|
|
+
|
|
|
|
|
+- 返回值优化(RVO):编译器优化直接构造对象到目标内存,消除临时对象
|
|
|
|
|
+- 命名返回值优化(NRVO):针对命名局部变量返回时的优化(C++17 起部分场景被强制要求)
|
|
|
|
|
+
|
|
|
|
|
+针对 `TestCls a(GetTestCls())` 预期行为是构建临时对象,调用移动构造。实际是编译器直接在 `a` 对象的内存位置构造对象,完全跳过了拷贝、移动构造
|
|
|
|
|
+
|
|
|
|
|
+针对 `TestCls a2 = GetTestCls()` 从 C++17 开始强制要求进行拷贝省略,等同于直接构造,无需任何拷贝、移动操作
|
|
|
|
|
+
|
|
|
|
|
+| 构造表达式 | C++ 标准 | 是否触发拷贝、移动构造 | 原因 |
|
|
|
|
|
+| --- | --- | --- | --- |
|
|
|
|
|
+| TestCls a(GetTestCls()) | <C++17 | 无 | RVO 优化 |
|
|
|
|
|
+| | ≥C++17 | 无 | 强制拷贝省略 |
|
|
|
|
|
+| TestCls a2 = GetTestCls() | <C++17 | 无 | 拷贝省略优化 |
|
|
|
|
|
+| | ≥C++17 | 无 | 强制拷贝省略 |
|
|
|
|
|
+
|
|
|
|
|
+#### 赋值规则
|
|
|
|
|
+
|
|
|
|
|
+左值、左值引用、右值、右值引用的赋值规则
|
|
|
|
|
+
|
|
|
|
|
+| | & | const & | && | const && |
|
|
|
|
|
+| --- | --- | --- | --- | --- |
|
|
|
|
|
+| 左值 | 可以 | 可以 | | |
|
|
|
|
|
+| const 左值 | | 可以 | | |
|
|
|
|
|
+| 右值 | | **可以** | 可以 | 可以 |
|
|
|
|
|
+| const 右值 | | 可以 | | 可以 |
|
|
|
|
|
+
|