首页 C++高级特性和性能优化
文章
取消

C++高级特性和性能优化

移动语义(C++11)

  • 编译器通过”移动“资源而不是复制它们,来优化临时对象和处理大型的数据结构
  • 两个关键概念:
    • 移动构造函数:参数是右值引用(&&表示),移动后,源对象通常处于有效但未指定的状态,也就是说这个对象的生命周期还没有结束,它仍然可以被赋予新的资源,或者被销毁,如果对象的生命周期结束,它的析构函数会被调用
    • 移动赋值运算符:类似移动构造函数,不过是把括号换成了等号
  • 对比值语义,值语义也就是复制资源再赋值,是内置类型(int float double…)的默认行为
  • RVO与移动区别:
    • MyClass b = MyClass(); 这行代码实际上涉及到了一个叫做“返回值优化”(Return Value Optimization,RVO)的编译器优化技术,而不是移动操作。为了提高效率,编译器可能会选择不创建这个临时对象,而是直接在 b 的存储位置构造这个对象

      两种语义的优缺点:

  • 移动语义优点:
    • 减少复制产生的开销,有利于处理大型数据结构或资源密集型对象
    • 简化资源管理(动态分配内存、文件句柄、网络连接)、防止资源泄露、并确保对象超出作用域或重置时被正确清理
    • 针对临时对象进行优化
  • 移动语义缺点:
    • 复杂,实现移动语义需要额外的代码,如移动构造函数和移动赋值函数
    • 有效但未指定的状态,移动后的源对象仍然可用,但内容可能已经无了,处理不当可能出意外
  • 值语义的优点
    • 简单易懂,代码少
    • 可预测,具有值语义的对象生命周期明确,在作用域结束时销毁
    • 独立,修改一个对象不会影响到另一个对象的状态
  • 值语义的切到吗
    • 对于大型数据结构和资源密集型对象,很慢
    • 不适合共享资源,例如不可赋值的文件句柄就不可能使用值语义对象

      std::forwarding

  • 允许函数模板将其参数完美转发给另一个函数,保留原始类型(包括是左值还是右值)
  • 如果 CreateObject 函数模板不使用 std::forward,那么即使 FString(“MyActor”) 是一个右值,它也会被当作左值传递给 FActor 的构造函数,这样就会调用左值引用版本的构造函数,而不是更高效的右值引用版本
  • 在非模板函数倒是不会这样,传的是啥就是啥,模板函数会这样是因为在 CreateObject 函数模板中,参数 args 被声明为 Args&& 类型,这是一个通用引用(或转发引用),它可以绑定到左值和右值。但是,一旦参数被绑定到这些通用引用上,它们就变成了左值,因为它们有了名字 ``` CPP template <typename T, typename… Args> T CreateObject(Args&&… args) { return T(std::forward(args)...); } class FActor { public: FActor(int32 id, FString&& name); // 右值引用构造函数 FActor(int32 id, const FString& name); // 左值引用构造函数 // ... };

FString actorName(“MyActor”); FActor actor1 = CreateObject(42, actorName); // 使用左值引用构造函数 FActor actor2 = CreateObject(42, FString("MyActor")); // 使用右值引用构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
### 在UE4中的应用
* 容器、字符串、指针、自定义类、结构体都可以,需要注意的是,在自定义类和结构体使用移动语义的时候需要自己实现移动构造函数和移动赋值操作符

## 编译时编程

* 模板、constexpr、静态断言、类型特征、模板元编程、内联函数和变量、常量折叠

### constexpr(C++11)

* 声明变量或者函数,在编译时计算表达式和计算值
  * 用计算值替换表达式
  * 简化、优化依赖这些常量值的代码,如循环展开、常量折叠
  * 删除从未执行的分支或条件

### 类型特征(C++11)

* 类型特征是一组模板和类,在编译时确定类型的属性和特征
* 需要头文件<type_traits>
* 如std::is_integral<T>、std::is_arithmetic<T>、std::is_same<T, U>、std::is_pointer<T>、std::enable_if<Condition,T>
```CPP
template <typename T>
typename std::enable_if<std::is_arithmetic<T>::value, T>::type
sum(T a, T b) {
    return a + b;
}
  • 上面代码意思是,检查T是否是算术类型,如果是,就可以使用这个模板函数,如果不是,就会得到一个编译时错误
  • std::enable_if<Condition,T>的Condition如果是True,那么就创建T这个type,其type成员就是T
  • std::is_arithmetic则是检查T是否为算术类型,如果是,那么其value成员就是True
  • 为什么要使用类型特征:
    • 实现模板的特化实现,从而提高性能
    • 允许根据类型属性在编译时决策,生成更优化的代码
    • 可以针对类型属性的优化,比如使用std::is_trivially_copyable检查一个类型是否可以通过简单的内存复制来复制其值。这种优化的一个例子是,当你需要复制一个大数组时,如果数组的元素类型是平凡可复制的,那么使用memcpy通常会比逐个元素调用复制构造函数要快得多。这是因为memcpy是专门优化过的,可以利用底层硬件的特性来快速移动大块内存。
    • 类型特征可与SFINAE(替换失败不是错误)一起使用,根据类型的属性有选择地启用或禁用某些函数或类模板特化。这可以帮助避免不必要的运行时检查或分支,从而提高代码效率
    • 编译时优化,生成更高效的代码
    • 更好的代码组织

      多态

      编译时多态

Lambda函数(c++11)

位集(bitset)

共享指针

容器

本文由作者按照 CC BY 4.0 进行授权