C++基础
C++基础
静态变量
静态变量的释放顺序是按照定义顺序的逆序执行的(和声明顺序没什么关系)。
全局静态变量
- 定义:全局静态变量是在文件的全局作用域中声明的静态变量,通常是在函数之外。
- 初始化:全局静态变量在程序启动时被初始化。它们的初始化顺序是固定的,并且在
main函数执行之前完成。 - 生命周期:全局静态变量的生命周期从程序开始到程序结束。它们在整个程序运行期间存在,知道程序退出时才会销毁。
- 释放:程序退出时,全局静态变量会被销毁。这个销毁发生在
main函数结束之后,但在程序的全局析构函数之前。
局部静态变量
- 定义:局部静态变量是在函数内部声明的静态变量。他们函数体内定义,但他们的作用域仅限于该函数。
- 初始化:局部静态变量在第一次调用其所在的函数时被初始化。初始化只发生一次,之后的调用将不会重新初始化。
- 生命周期:局部静态变量的生命周期从第一次初始化到程序结束。虽然它们的作用域限制在函数内,但是它们的内存从第一次初始化到程序结束一直存在。
- 释放:局部静态变量在程序退出时销毁。在
main函数结束时,所有局部静态变量会被销毁。
智能指针
析构函数调用时机
智能指针释放的时候会先减计数,随后根据计数是否为零,来调用析构函数。
有符号数和无符号数的类型转换
有符号数到无符号数:
- 如果有符号数为正数则直接转换为对应的无符号数。
- 如果有符号数为负数,则转换后的结果会加上无符号数的最大值再加1,以得到对应的无符号数值。 无符号数到有符号数:
- 如果原始的无符号数数值小于等于有符号数的最大值,则直接转换。
- 如果原始的无符号的值大于有符号的最大值,则转换之后结果溢出,取模运算,相当于减去无符号数的最大值再减1。
固定点小数(fixed-point fractional)
表示一种表示和处理小数的方法,特别是在没有浮点数处理能力或更高效处理的环境中。它通过固定小数位数来表示小数部分,而不像浮点数那样动态调整小数点的位置。
具体来说,固定点小数的数据通常以整数形式存储,但是按照实现约定的规则进行解释,以便正确计算和表示小数值。
这种方法通常在某些嵌入式系统、低成本计算机或需要高效数值计算的场合中比浮点数更具优势,因为它通常需要更少的计算资源和更少的存储空间。
原码和补码
原码: 是一种整数的表达方式,即用二进制表示整数的绝对值,使用最高位(符号位)表示符号,0表示正数,1表示负数。例如十进制数+5表示为00000101,-5表示为10000101。
补码: 补码是在原码的基础上发展而来的一种表达方式。规则为:正数的补码和其原码相同,负数的补码为其正数的原码取反加一。例如:十进制+5的补码为00000101,-5的补码为11111011
补码的好处:
- 唯一表示零: 0的补码唯一
- 简化加法运算: 在加法运算中,补码的加法规则与原码和反码相同,不需要额外处理。
位操作
XOR(异或)
异或的特性:
- $a\oplus0=a$。
- $a\oplus a=0$。
- 满足交换律、结合律。
常用场景
C++中是符号^。
- 对于“一个数组中,除了一个数,其他数都出现了两次”的这种问题,使用异或操作整个数组,就可以得到这个唯一的数字(异或的交换律和结合律)。
- 对于“一个数组中,除了两个数,其他数都出现了两次”的这种问题,需要进行分组异或,分组的界限是整体异或的非零位,可以通过
all_xor & (-all_xor)获得最右边的非零位。
内存地址
位(bit)
一个二进制位,只有0和1。
字节(byte)
一个字节由8bit组成,通常是内存寻址的基础单位。
字(word)
字是指计算机处理数据的自然单位,其长度取决于计算机或处理器的架构:
- 16位架构的系统:一个字表示16bit
- 32位架构的系统:一个字表示32bit
- 64位架构的系统:一个字表示64bit
字节寻址(byte addressing)
常规CPU是按照字节(byte)进行寻址的,意味着每个内存地址对应一个字节的数据。
大端小端
- 小端(Little Endian):最低有效字节(Least Significant Byte, LSB)存储在最低地址处。
- 大端(Big Endian):最高有效字节(Most Significant Byte, MSB)存储在最低地址处。
直观理解:从地址首位开始,大端表示最高位字节在首位,顺序下排,小端表示最低位字节在首位,顺序下排。例如有一个32位整数0x12345678,其大端内存排列方式为:
大端:
| 地址 | 内存内容 |
|---|---|
| 0x1000 | 0x12 |
| 0x1001 | 0x34 |
| 0x1002 | 0x56 |
| 0x1003 | 0x78 |
| 小端: |
| 地址 | 内存内容 |
|---|---|
| 0x1000 | 0x78 |
| 0x1001 | 0x56 |
| 0x1002 | 0x34 |
| 0x1003 | 0x12 |
4096(4KB)对齐
4KB对齐的含义是,内存的起始地址是4KB的整数倍,例如p是指向一个4KB对齐的地址,那么满足p % 4096 == 0。例如0x1000、0x2000、0x3000都是4KB对齐的地址。
- 虚拟内存管理优化 操作系统通常使用分页机制管理虚拟内存,常见的页面大小是4KB,所以4KB对齐确保内存块恰好与页面边界对齐,便于分页和转换。而且对齐后的内存可以直接映射到页表条目中,提高了内存的管理效率。
- 硬件优化 许多硬件使用缓存进行数据传输和处理。4KB对齐确保内存块在缓存行之间分布合理,减少了缓存未命中(cache miss)的现象,提高了缓存的利用率。
- DMA传输 DMA控制器通常要求内存地址对齐以提高传输效率,减少总线占用时间。
符号导出
- 下述
CAPI_EXPORT定义了在不同平台导出符号的写法,主要是为了让其生成的动态库可以被外部调用。 extern "C"是一个链接规范,告诉C++编译器使用C语言的链接方式处理特定函数或者变量。- 防止名称修饰:C++编译器在编译时会对函数名进行修饰(也称为“名称混乱”),以支持函数重载等特性。例如,
int func(int)可能被编译成类似func__Fi的名字。使用extern "C"可以防止这种修饰,使得编译器按C语言的方式处理函数名,从而保证C和C++编译器生成的符号名称一致。 - 跨语言调用:当你需要从C++代码中调用C语言库中的函数,或让C语言代码调用你的C++函数时,
extern "C"是必不可少的。它确保C++编译器生成的符号名称与C语言兼容,从而实现跨语言调用。
- 防止名称修饰:C++编译器在编译时会对函数名进行修饰(也称为“名称混乱”),以支持函数重载等特性。例如,
#if defined(_WIN32)
#define CAPI_EXPORT __declspec(dllexport)
#else
#define CAPI_EXPORT __attribute__((visibility("default")))
#endif // _WIN32
// header.h
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
CAPI_EXPORT void printSomething();
#ifdef __cplusplus
}
#endif // __cplusplus
互斥量mutex
C++中,互斥量(std::mutex)的管理通常使用RAII(Resource Acquisition Is Initialization)封装类。 常见的有: std::lock_guard:
- 简单:是一个轻量级的互斥量管理器设计用来提供简单的、不可重新加锁的互斥量保护。
- 构造与析构:初始化即锁定互斥量,析构时自动释放。
- 不可手动解锁:只能通过析构时解锁,不能手动解锁。 std::unique_lock:
- 功能丰富:除了提供基本的锁定解锁功能(不调用接口和不加额外参数的情况下行为和
std::lock_guard一致),还可以延迟锁定,提前解锁,重新锁定。 - 构造与析构:构造时可以选择
- 延迟锁定:
unique_lock(mutex_type& mtx, std::defer_lock_t);。 - 选择性锁定:
unique_lock(mutex_type& mtx, std::try_to_lock_t);,尝试锁定,如果失败则返回一个不带有锁的std::unique_lock对象。 - 超时锁定:
unique_lock(mutex_type& mtx, std::chrono::duration<Rep, Period> timeout);,timeout表示在该时间段内尝试锁定互斥量。 - 接管:
unique_lock(mutex_type& mtx, std::adopt_lock_t);,接管由其他std::unique_lock或线程锁定的互斥量。
- 延迟锁定:
- 灵活性:支持显示的锁定(
lock())和解锁(unlock())。 std::shared_lock: - 与
std::shared_mutex配合使用,支持共享锁机制:使多个线程可以同时读取共享资源,但是只有一个线程可以写入资源(配合独占锁std::unique_lock使用)。 - 当多个线程获取
std::shared_lock时,一个线程想要获取std::unique_lock则需要等待所有std::shared_lock释放,同样的,如果一个线程获取了std::unique_lock,其他的线程不论是要获取std::shared_lock还是std::unique_lock都要等;但是如果一个线程获取了std::shared_lock,另外一个线程想要获取std::shared_lock则可以立即获得。
std::atexit
该函数用于注册一个在程序正常终止时调用的函数,eg:main return,注册的函数调用在静态资源释放之前,所以对于某些依赖程序退出释放的静态资源很有用,可以避免遇到静态资源释放顺序问题 OpenCL静态资源释放问题。
该函数只会将函数注册到程序正常退出的时候,对于dlclose等库关闭的事件并不会响应,对于dlclose的事件需要用别的机制来实现。
面向对象
多态(Polymorphism)
同一份指令(同一个接口或函数调用),作用于不同类型的对象时,可以产生不同的行为和结果。
如果没有多态,一份代码可能会是这样子:
// 伪代码,没有多态的情况
void pressPlay(Device* device) {
if (device->type == TV) {
((TV*)device)->playShow();
} else if (device->type == BluRayPlayer) {
((BluRayPlayer*)device)->playDisc();
} else if (device->type == SoundSystem) {
((SoundSystem*)device)->playMusic();
}
// 如果新增一个设备,比如 Projector(投影仪)
// 你就必须回来修改这个函数!
else if (device->type == Projector) {
((Projector*)device)->startProjection();
}
}这种代码有几个致命的缺点:
- 难以维护:每次新增设备,都要增加
else if逻辑判断。 - 耦合度高:调用者代码(
pressPlay函数)与具体的实现类紧密的绑定在一起。 - 扩展性差:不符合"对扩展开放,对修改关闭"的开闭原则。 多态的出现就是为了解决这个问题,它让我们可以编写更通用、更灵活、更易于扩展的代码。
广义上的多态分为两类:
- 静态多态(Static Polymorphism)
- 函数重载、模板
- 编译时确定调用哪个函数,效率更高。
- 动态多态(Dynamic Polymorphism)
- 虚函数,继承,基类指针/引用调用
- 程序运行时根据
vptr指向的vtable来决定调用哪个函数。
C++中的多态,主要讨论的是运行时多态,主要涉及到:
- 继承(Inheritance):必须存在一个基类和至少一个派生类。派生类继承其基类的接口。
- 为多态提供了类型层次结构的基础。
- 虚函数(Virtual Functions):在其基类中,使用
virtual关键词修饰的成员函数。这是实现多态的核心。- 当一个成员函数被声明为
virtual的时候,编译器会为这个类创建一个虚函数表(vtable)。vtable本质上是一个函数指针数组,存储着类中所有虚函数的实际地址。每个包含虚函数的类的对象都会在内存中包含一个指向其类vtable的指针(通常称谓vptr)。
- 当一个成员函数被声明为
- 基类指针或引用(Base Class Pointers or References):通过指向派生类对象的基类指针或引用来调用虚函数。
- 当你通过基类指针调用一个虚函数时(例如
animal_ptr->makeSound()),程序不会在编译时决定调用哪个函数,而是在运行时,通过对象的vptr找到对应的vtable,再从vtable中查找并调用正确版本的makeSound函数(是Dog的,还是Cat的)。这个过程被称为动态绑定(Dynamic Binding) 或后期绑定(Late Binding)。
- 当你通过基类指针调用一个虚函数时(例如
C++11
左值(lvalue)右值(rvalue)
左值是一个表达式,它指向内存中的一个对象,并且可以标识为一个具体的存储位置。通常可以取地址的表达式都是左值。
- 可以被取地址。左值可以用
&符号取地址。 - 具有持久性。 左值通常是一个具值对象,它们具有持久性,可以被多次使用。
- 可以作为赋值目标。 可以在赋值操作符
=的左侧使用。
右值是一个表达式,它代表临时的、即将被销毁的值,通常没有具体的内存位置。以下为右值的特征。
- 不能被取地址:不能用取地址符号获取其在内存中的地址。
- 临时性:右值表达式通常是临时创建的,他们的生命周期通常非常短,可能在表达式结束的时候立即销毁。
- 通常是字面量、临时对象或移动语义生成的结果:例如整数常量、字符串字面量、临时对象和移动构造函数生成的临时值等。
decltype
decltype(发音deck-ul-type),是C++11引入的一个关键字,用于查询表达式的类型。它可以根据表达式来推导出类型,在模板编程、类型推导和泛型编程中非常有用。
decltype(expression) variableName;规则:
- 如果
expression是一个变量,decltype(expression)返回的就是该变量的类型。 - 如果
expression是一个左值表达式,则decltype(expression)返回的是左值引用类型。 - 如果
expression是一个右值表达式,则decltype(expression)返回的是该表达式的类型。
// 推理参数类型
int x = 0;
int& y = x;
decltype(x) a; // int类型
decltype(y) b = a; // int& 类型
decltype((x)) c = x; // int& 类型,因为(x)是左值表达式
decltype(x*y) d = x; // int 类型,x*y为右值表达式,所以是右值类型。
// 推理模板函数返回值
template<typename T0, typename T1>
auto test(T0 a, T1 b) -> decltype(a * b) {
...
}
// 泛型函数中,可以用来定义复杂类型或表达式的类型,从而使代码更加灵活和通用。
template<typename Container>
auto accessElement(Container& container, size_t index) -> decltype(container[index]) {
return container[index];
}变长模板参数
变长模板参数可以用于:
- 定义任意参数类型/数量输入的模板函数。(递归展开参数包,缺点是编译时耗时且二进制相对会大一些)。
- 参数转发(将参数完美的转发给其他函数)
不定参数函数定义:
#include <iostream>
// 首先定义终止函数(也就是参数为空的时候的函数)
void printInternal() {
std::cout << std::endl;
}
// 然后定义解包函数
template<typename First, typename... Rest>
void printInternal(const First& first, const Rest&... rest) {
std::cout << first << " ";
printInternal(rest...);
}
// 定义入口函数
template<typename... Args>
void printUnlimited(const Args&... args) {
printInternal(args...);
}参数转发:
typename... Args是参数包,也是变长模板函数的核心- 通常函数中
&&定义的参数为右值,但是在模板函数中Func&&通常表示转发引用。这意味着Func&&可以绑定到左值或者右值,也就是不论你传递给模板函数的是一个左值对象还是右值对象,Func&&都能正确的捕获/绑定。 std::forward可以将参数完美转发出去。通常使用func(args...),参数很可能丢失原始值的类别(左值右值),导致不必要的拷贝和移动操作。使用std::forward<T>(arg),可以保持原始值的类别:如果arg是一个左值,std::forward<T>(arg)返回一个左值引用;如果arg是一个右值,std::forward<T>(arg)返回一个右值引用。
// 函数参数转发
template<typename Func, typename... Args>
auto invoke(Func&& func, Args&&... args) -> decltype(std::forward<Func>(func)(std::forward<Args>(args)...)) {
return std::forward<Func>(func)(std::forward<Args>(args)...);
}
// 示例 模板中&&表示转发引用,不论传递给模板的是左值/右值,都能保持其类型。
template<typename T>
void target(T&& arg) {
std::cout << "Processing argument: " << arg << std::endl;
}
template<typename T>
void wrapper(T&& arg) {
target(std::forward<T>(arg));
}
int main () {
int x = 10;
wrapper(x); // 传递左值 x
wrapper(20); // 传递右值 20
return 0;
}类成员初始化
C++11有以下几种类的成员函数初始化方式:
// 声明时直接初始化,使用`=`
class TestClass {
private:
std::string str = "default";
...
};
// 声明时直接初始化,使用`{}`,这种初始化方式是推荐的方式,称为 统一初始化 语法(Uniform Initialization Syntax)
class TestClass {
private:
std::string std{"default"};
};
// 构造函数列表初始化,使用`()`
class TestClass {
private:
std::string str;
public:
TestClass(): str("default") {}
};
// 构造函数列表初始化,使用`{}`
class TestClass {
private:
std::string str;
public:
TestClass(): str{"default"} {}
};
// 构造函数内部初始化,这个就不用讲了,直接省略吧
class TestClass {
private:
std::string str;
public:
TestClass() {
str = "default";
}
};constexpr
constexpr是C++11引入的关键字,用于尽可能地把运算从运行时(runtime)提前到编译时(compile-time),从而提升程序性能、提供更强的编译期检查并开启更强大的元编程能力。- 可以理解为常量表达式(const expression)的缩写。
- 其值必须在编译时可知。 相比于const更加的const。
- 主要有两种用法:
constexpr常量;constexpr函数。其中constexpr函数是其最强大的地方,一个constexpr有双重身份:- 当
constexpr常量作为参数调用constexpr函数的时候,编译器会在编译时执行这个函数,并用返回值替换函数调用。 - 当用变量作为参数调用
constexpr函数的时候,他就和普通函数一样,被编译成机器码,也在运行时执行。 - 限制:1)C++11中,
constexpr函数体内只能有一个return语句。2)C++14放宽了条件,函数内可以包含循环、分支、局部变量等,只要保证所有逻辑在给定constexpr常量时,能在编译器产生结果即可。3)函数不能有I/O操作、不能抛出异常、不能使用static、thread_local变量。
- 当
系统库
GLIBCXX
GLIBCXX是GNU C++ Standard Library(也称为libstdc++)的一部分他是GCC(GNU Compiler Collection)项目的一部分,提供了C++标准库的实现,包括STL(Standard Template Library)、输入输出流、算法和其他C++标准库的组件。
该库也是向下兼容的,GLIBCXX_3.4.30是包含版本以下的符号的。
编译器
clang
-fno-rtti
用于禁用C++运行时类型信息(RTTI)。RTTI是一种使得程序在运行时识别对象类型的机制。它支持C++的dynamic_cast和typeid操作符。使用-fno-rtti可以减少二进制文件的大小并可能提高性能,因为这个编译选项会移除RTTI相关的数据和代码。
代码内编译选项
// android_aosp/system/core/libcutils/include/cutils/native_handle.h:39
#if defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wzero-length-array"
#endif
int data[0]; /* numFds + numInts ints */
#if defined(__clang__)
#pragma clang diagnostic pop
#endifgcc
版本切换
# 举个例子我现在ubuntu上有GNU9 版本的编译器,但是我的工程需要用GNU7.5
# 先安装GNU7.5的gcc
sudo apt-get install gcc-7 g++-7
# 随后使用 update-alternateives 更新默认版本
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-7 70
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-7 70
# 或者通过环境变量指定
CC=gcc-7 CXX=g++-7 cmake ..
cmake -DCMAKE_C_COMPILER=gcc-7 -DCMAKE_CXX_COMPILER=g++-7 ..-flto问题
是在编译OpenCV的时候遇到的问题:
c++: error: -flto: No such file or directory
# Visual Studio
-G to -T
| VS版本 | -G | -T |
|---|---|---|
| Visual Studio 2022 | “Visual Studio 17 2022” | “v143” |
| Visual Studio 2019 | “Visual Studio 16 2019” | “v142” |
| Visual Studio 2017 | “Visual Studio 15 2017” | “v141” |
| Visual Studio 2015 | “Visual Studio 14 2015” | “v140” |
符号冲突(eg: max)
windows.h通常会定义一些符号(比如min、max)导致和c++标准库发生冲突,这种情况下需要在#include "windows.h"之前增加#define NOMINMAX符号,解决这个问题。
ccache
ccache 是一个编译缓存工具,主要用于加速C/C++代码的编译过程。它通过缓存编译过的输出,避免重复编译相同的源代码,从而减少编译时间。
主要功能和工作原理
- 缓存编译结果: 当你编译一个C/C++文件时,
ccache会缓存编译器生成的目标文件和相关的编译信息。如果你再次编译相同的源文件,并且源文件及其依赖项没有变化,ccache会直接从缓存中返回上次编译的结果,而不需要再次调用编译器。 - 哈希计算:
ccache使用输入文件内容、编译器选项和其他相关信息生成一个哈希值。这个哈希值用于索引缓存。如果同样的输入生成了相同的哈希值,ccache就会从缓存中获取编译结果。 - 命中与未命中:
- 命中(Hit): 当
ccache检测到缓存中已有对应的编译结果时,直接使用缓存结果,节省编译时间。 - 未命中(Miss): 如果
ccache没有找到对应的缓存结果,或者源文件发生了变化,则会进行正常的编译并更新缓存。
- 命中(Hit): 当
通用机制
__attribute__
__attribute__是GCC、Clang等编译器的一种扩展机制,用于为函数、变量、类型或结构体等添加特定属性。这些属性是编译器指令,可以影响代码生成、优化、安全性检查等行为。
函数属性
__attribute__((noreturn)):告诉编译器该函数不会返回,例如exit()或abort()__attribute__((format(printf, 1, 2))):用于检查函数的参数是否符合printf风格的格式化字符串规范。__attribute__((deprecated)):标记函数为过时,使用该函数时会产生编译警告。__attribute__((weak)):定义弱符号,允许多个同名符号共存,链接时可以被其他强符号覆盖。__attribute__((visibility("default"))):控制符号的可见性,default表示默认可见,hidden表示隐藏,protected表示当前共享库内可见,但是不能被外部共享库重写,internal表示仅当前编译单元可见。通常配合编译选项-fvisibility=hidden,在cmake里面就是:
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -fvisibility=hidden")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fvisibility=hidden")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -fvisibility-inlines-hidden")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fvisibility-inlines-hidden")变量属性
__attribute__((aligned(n))):指定变量或结构的对齐方式为n字节。__attribute__((packed)):使结构体的成员按最紧密的方式对齐,不进行默认的字节填充。
平台判断
#if defined(_WIN32) || defined(_WIN64)
// Windows platform
#elif defined(__linux__)
// Linux platform
#elif defined(__ANDROID__)
// Android platform
#elif defined(__APPLE__) && defined(__MACH__)
#if TARGET_OS_IOS
// ios platform
#elif TARGET_OS_MAC
// macOS platform
#endif
#endifAddressSanitizer
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer -g")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer -g")堆栈查看
# Android Native端,使用AddressSanitizer导出的日志通常是 .so+偏移
# 如果要得到实际代码位置需要利用addr2line
addr2line -e yourlib.so <偏移>
# 如果要批量得到可以写脚本修改.so的名称和链接依赖
# Eg:我现在有两个库libCNamaSDK.so和libfuai.so
# 其中link关系是libCNamaSDK.so <-- libfuai.so
# 现在我想修改库名,并且保证链接的时候使用新名称不会出问题
# libCNamaSDK.so -> libCNamaSDK_Camera.so
# libfuai.so -> libfuai_Camera.so
# 使用patchelf指令
# 1. 修改库名
mv libCNamaSDK.so libCNamaSDK_Camera.so
mv libfuai.so libfuai_Camera.so
# 2. 修改elf信息
# 2.1 修改每个库的soname
# patchelf --set-soname <新SONAME> <目标文件>
patchelf --set-soname libCNamaSDK_Camera.so libCNamaSDK_Camera.so
patchelf --set-soname libfuai_Camera.so libfuai_Camera.so
# 2.2 修改链接信息
# patchelf --replace-needed <旧库名称> <新库名称> <目标文件>
patchelf --replace-needed libfuai.so libfuai_Camera.so libCNamaSDK_Camera.so查看ELF依赖的.so
LD_LIBRARY_PATH=<your_libs_path> ldd <absolute_path_to_your lib>Code Examples
自动释放资源的单例
#include <iostream>
#include <memory>
#include <mutex>
#include <vector>
class Engine {
public:
enum class MemoryPolicy { PerformanceEfficient = 0, MemoryEfficient = 1 };
static void setMemoryPolicy(MemoryPolicy policy) { _policy = policy; }
static std::shared_ptr<Engine> createInstance() {
std::shared_ptr<Engine> instance = nullptr;
std::lock_guard<std::mutex> look(_mtx);
if (_w_ptr.lock() == nullptr) {
// Get no engine, init the engine.
instance = std::shared_ptr<Engine>(new Engine);
_w_ptr = instance; // assign instance to weak_ptr.
if (_policy == MemoryPolicy::PerformanceEfficient) {
// PerformanceEfficient
// Decrease the ref count of instance until program exit or called
// Engine::releaseInstance()
_ptr = instance;
} else {
// MemoryEfficient
// Directly release the instance if no outside reference.
_ptr = nullptr;
}
// Register the release function to be called at program exit (when main
// returns) to avoid issues caused by the release order of static
// variables in libOpenCL.so.
std::atexit(Engine::releaseInstance);
} else {
// Memory effecient, remove the reference of _ptr.
instance = _w_ptr.lock();
}
return instance;
}
static void releaseInstance() {
std::lock_guard<std::mutex> lock(_mtx);
_ptr = nullptr;
}
static void printRefCount() {
std::lock_guard<std::mutex> lock(_mtx);
std::cout << "Current reference: " << _w_ptr.use_count() << ", "
<< _w_ptr.lock() << std::endl;
}
~Engine() {
// 析构函数只会在智能指针引用计数为零的时候调用,所以_w_ptr.use_count()必须为0.
std::cout << "Release the engine, " << _w_ptr.use_count() << std::endl;
}
private:
Engine() { std::cout << "Init the engine. " << std::endl; }
static std::mutex _mtx; // mutex
static std::shared_ptr<Engine> _ptr;
static std::weak_ptr<Engine> _w_ptr;
static MemoryPolicy _policy;
};
std::weak_ptr<Engine> Engine::_w_ptr;
std::shared_ptr<Engine> Engine::_ptr = nullptr;
std::mutex Engine::_mtx;
Engine::MemoryPolicy Engine::_policy =
Engine::MemoryPolicy::PerformanceEfficient;
int main() {
Engine::setMemoryPolicy(Engine::MemoryPolicy::PerformanceEfficient);
{
std::shared_ptr<Engine> engine0 = Engine::createInstance();
Engine::printRefCount();
}
{
std::shared_ptr<Engine> engine1 = Engine::createInstance();
Engine::printRefCount();
}
{
std::shared_ptr<Engine> engine2 = Engine::createInstance();
Engine::printRefCount();
}
Engine::printRefCount();
return 0;
}记时
#include <chrono>
#include <iostream>
// 直接计时
auto start = std::chrono::high_resolution_clock::now()
...
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<sstd::chrono::milliseconds>(end - start);
std::cout << "Timer: " << duration.count() << std::endl;
// 析构函数+宏
class _TimerInternal {
public:
_TimerInternal(const std::string &timer_name)
: _name(timer_name), _start(std::chrono::high_resolution_clock::now()) {}
~_TimerInternal() {
_end = std::chrono::high_resolution_clock::now();
int64_t time =
(std::chrono::duration_cast<std::chrono::microseconds>(_end - _start))
.count();
std::cout << _name << " timer: " << time / 1000.f << " ms" << std::endl;
}
private:
std::string _name;
decltype(std::chrono::high_resolution_clock::now()) _start;
decltype(std::chrono::high_resolution_clock::now()) _end;
};
// ##name放最后,有些编译器没法处理放前面的情况。
#define STACK_TIMER(name) auto _timer_##name = _TimerInternal(#name);
#define STACK_TIMER_START(name) \
auto _timer_ptr_##name = std::make_shared<_TimerInternal>(#name);
#define STACK_TIMER_END(name) _timer_ptr_##name = nullptr;计算文件大小
#include <fstream>
std::ifstream file("files.txt", std::ios::binary);
file.seekg(0, std::ios::end); // 到文件尾
size_t file_size = file.tellg();
file.seekg(0, std::ios::beg); // 返回文件首
file.close();读取整个文件
#include <fstream>
#include <sstream>
// 一个先计算文件大小创建文件buffer,随后调用文件的read接口
// 另外一个就是利用sstream
std::ifstream file("files.txt", std::ios::binary);
std::stringstream stream;
stream << file.rdbuf();
stream.str();Sleep
#include <thread>
#include <chrono>
...
// std::chrono::second, std::chrono::millisecond, std::chrono::microsecond
std::this_thread::sleep_for(std::chrono::second(2));
...随机数
#include <random>
...
// 创建随机数生成器
std::random_device rd;
std::mt19937 gen(rd());
// 定义一个范围在[1, 100]之间的分布
std::uniform_int_distribution<> dis(1, 100);
// 生成随机数
int random_number = dis(gen);
...长文本格式化定义
const char *mad_kernel_source =
R"(__kernel void mad_8uTo8u_elm4(__global const uchar *srcptr,
__global uchar *dstptr, int elm_size,
float multipler, float addend) {
int offset = get_global_id(0) * 4;
if (offset + 4 <= elm_size) {
float4 pix = convert_float4(vload4(0, srcptr + offset));
pix.x = mad(pix.x, multipler, addend);
pix.y = mad(pix.y, multipler, addend);
pix.z = mad(pix.z, multipler, addend);
pix.w = mad(pix.w, multipler, addend);
vstore4(convert_uchar4_sat_rtz(pix), 0, dstptr + offset);
} else if (offset + 3 == elm_size) {
float3 pix = convert_float3(vload3(0, srcptr + offset));
pix.x = mad(pix.x, multipler, addend);
pix.y = mad(pix.y, multipler, addend);
pix.z = mad(pix.z, multipler, addend);
vstore3(convert_uchar3_sat_rtz(pix), 0, dstptr + offset);
} else if (offset + 2 == elm_size) {
float2 pix = convert_float2(vload2(0, srcptr + offset));
pix.x = mad(pix.x, multipler, addend);
pix.y = mad(pix.y, multipler, addend);
vstore2(convert_uchar2_sat_rtz(pix), 0, dstptr + offset);
} else if (offset + 1 == elm_size) {
(dstptr + offset)[0] = convert_uchar_sat_rtz(
mad(convert_float((srcptr + offset)[0]), multipler, addend));
}
})";