前言

C++的特性多的数不胜数,语言标准也很多,所以不定期对近期所学的C++知识进行总结,是对自身知识体系检查的良好机会,顺便锻炼一下写博客的文笔

三/五/零之法则

三之法则:如果某个类需要用户定义的析构函数、用户定义的复制构造函数或用户定义的复制赋值运算符,那么它几乎肯定需要全部三者。

五之法则:任何想要移动语义的类必须声明全部五个特殊成员函数(析构函数、拷贝构造、赋值运算、移动拷贝构造、移动赋值运算)

零之法则:有自定义析构函数、复制/移动构造函数或复制/移动赋值运算符的类应该专门处理所有权

当有意将某个基类用于多态用途时,可能需要将它的析构函数声明为公开的虚函数。由于这会阻拦隐式移动(并弃用隐式复制)的生成,因而必须将各特殊成员函数声明为预置的

class base_of_five_defaults
{
public:
base_of_five_defaults(const base_of_five_defaults&) = default;
base_of_five_defaults(base_of_five_defaults&&) = default;
base_of_five_defaults& operator=(const base_of_five_defaults&) = default;
base_of_five_defaults& operator=(base_of_five_defaults&&) = default;
virtual ~base_of_five_defaults() = default;
};

扩展阅读:

来自cppreference:三五法则

CRTP

  • Curiously Recurring Template Pattern(奇异的递归模板模式)

CRTP是指一个类A有一个基类,这个基类是类A本身的模板特化。具有编译时多态的特性

如下例子也可通过vtable实现。拿这个例子,将CRTPvtable实现的动态多态进行对比

虚函数:

内存:每个虚函数一个函数指针

运行时:一次函数指针调用

而 CRTP 静态多态的开销是:

而 CRTP 静态多态的开销是:

内存:每个模板实例化的 Base 副本

运行时:一个函数指针调用 + static_cast 正在做的任何事情

template <typename T>
struct Base {
void foo() {
(static_cast<T*>(this))->foo();
}
}; struct Derived : public Base<Derived> {
void foo() {
cout << "derived foo" << endl;
}
}; struct AnotherDerived : public Base<AnotherDerived> {
void foo() {
cout << "AnotherDerived foo" << endl;
}
}; template<typename T>
void ProcessFoo(Base<T>* b) {
b->foo();
} int main()
{
Derived d1;
AnotherDerived d2;
ProcessFoo(&d1);
ProcessFoo(&d2);
return 0;
}

Output:

derived foo
AnotherDerived foo

扩展阅读:

来自cppreference:CRTP

c++标准中对于CRTP的使用例子:std::enable_shared_from_this(cpp11)std::ranges::view_interface(cpp20)

RAII

  • Resource Acquisition Is Initialization(资源获取即初始化)

将资源的生命周期与对象的生命周期所绑定(构造获取资源/析构释放资源,利用了栈上的变量在离开作用域的时候会析构的特性),c++11后的四大smart_point(shared_ptrunique_ptrweak_ptrauto_ptr(在17中废除))采用了这种思想。

扩展阅读:

一文带你了解智能指针(转载并结合总结)

RTTI

  • Run Time Type Identification(运行时类型识别)
  • c++中RTTI的一些体现typeiddynamic_casttype traits

    具体可以看runtime的库的函数__RTtypeid,rtti把所需的type_info信息放在vtable前,大概也是dynamic_cast要求父类必须有虚函数的原因吧
  • 注意,取虚函数表地址时(此处请注意环境在32位和64位下的区别,32位可以用int,64位用longlong)
...
Base *pb2 = new Derive();
const std::type_info &tp2 = typeid(*pb2);
printf("tp2地址为:%p\n", &tp2);
long *pvptr = (long *)pb2;
long *vptr = (long *)(*pvptr);
printf("虚函数表首地址为:%p\n", vptr);
printf("虚函数表首地址之前一个地址为:%p\n", vptr-1); //这里的-1实际上是往上走了4个字节 long *prttiinfo = (long *)(*(vptr - 1));
prttiinfo += 3; //跳过12字节
long * ptypeinfoaddr = (long *)(*prttiinfo);
const std::type_info *ptypeinfoaddrreal = (const std::type_info *)ptypeinfoaddr;
printf("ptypeinfoaddrreal地址为:%p\n", ptypeinfoaddrreal);
cout << ptypeinfoaddrreal->name() << endl;
...

扩展阅读:

C++ RTTI 实现原理详解

(C++对象模型):RTTI运行时类型识别回顾与存储位置介绍

【专业技术】C++ RTTI及“反射”技术

RTTR

  • 反射是一个进程检查、反省和修改其自身结构和行为的能力
  • Run Time Type Reflection(运行时类型反射)

    众所周知,java、c#、Go等语言在语言层面支持了反射特性。而c++不支持反射,因为C++没有在语言层面提供返回类的metadata的能力,所以很多属性要靠手动注册,于是乎有人自造轮子搞了个反射机制(UE中的U++通过UHT和UBT来支持反射)

扩展阅读:

Run Time Type Reflection

C++ Reflection Library

auto接收std::vector<bool>::reference的问题

注意此处的BoolData类型是std::vector\<bool\>::reference,此处是历史遗留问题,设计std::vector\<bools\>的时候,认为bool只需要1bit,内部做了内存优化,所以用[]访问的时候,得到的是一个内部(被压了位)对象的引用

如果在长度确定的情况下,用std::bitset代替std::vector是一个更好地选择

    std::vector<bool> BoolDatas;

    // BoolData: std::vector<bool>::reference
for (auto BoolData : BoolDatas)
{
} // IntData: int
std::vector<int> IntDatas;
for (auto IntData : IntDatas)
{
}

扩展阅读:

cppreference: std::vector::reference

类型擦除

将原有类型消除或者隐藏,换言之,在封装接口中,很多情况下我不关心具体类型是什么或者根本不需要这个类型,它可以使接口有更好的通用性、延展性,消除耦合,减少重复代码

  • 一个很详细关于类型擦除的介绍:类型擦除,从多态、template、std::varient(来自boost::varient)、std::any(来自boost::any)、到closesure去分析

扩展阅读:

类型擦除

boost

只能说boost yyds啊,除了模板多,多次编译会导致编译时间长以外,功能真的很强大 确实如其名boost。例如c++17中的std::filesystemstd::anystd::varient直接来自于boost中。还有boost::program_options用于处理控制台的输入参数也是很方便

#、#@、##、__VA_ARGS__ 应用

#define Conn(x,y)  x##y // 表示x连接y
#define ToChar(x) #@x // 给x加上单引号
#define ToString(x) #x // 给x加上双引号

#

char* str = ToString(123132);     // str="123132";

##

int n = Conn(123,456);            //n=123456;
char* str = Conn("asdf", "add") //str = "asdfadf";

也可用来省略可变参数为空时,去掉前面的,

#define ESC_START     "\033["
#define ESC_END "\033[0m"
#define COLOR_FATAL "31;40;5m"
#define COLOR_ALERT "31;40;1m"
#define COLOR_CRIT "31;40;1m"
#define COLOR_ERROR "31;40;1m"
#define COLOR_WARN "33;40;1m"
#define COLOR_NOTICE "34;40;1m"
#define COLOR_INFO "32;40;1m"
#define COLOR_DEBUG "36;40;1m"
#define COLOR_TRACE "37;40;1m" #define Msg_Info(format, ...) (printf( ESC_START COLOR_INFO "[INFO]-[%s]-[%s]-[%d]:" format ESC_END, __FILE__, __FUNCTION__ , __LINE__, ##__VA_ARGS__))
#define Msg_Debug(format, ...) (printf( ESC_START COLOR_DEBUG "[DEBUG]-[%s]-[%s]-[%d]:" format ESC_END, __FILE__, __FUNCTION__ , __LINE__, ##__VA_ARGS__))
#define Msg_Warn(format, ...) (printf( ESC_START COLOR_WARN "[WARN]-[%s]-[%s]-[%d]:" format ESC_END, __FILE__, __FUNCTION__ , __LINE__, ##__VA_ARGS__))
#define Msg_Error(format, ...) (printf( ESC_START COLOR_ERROR "[ERROR]-[%s]-[%s]-[%d]:" format ESC_END, __FILE__, __FUNCTION__ , __LINE__, ##__VA_ARGS__)) int main()
{
Msg_Info("test!\n");
Msg_Warn("%d\n", 10);
Msg_Error("%s\n", "error");
Msg_Debug("Debug\n");
// 当可变参数为空时
Msg_Debug(); /*
(printf( "\033[" "32;40;1m" "[INFO]-[%s]-[%s]-[%d]:" "test!\n" "\033[0m", "D:\\repos\\C++Project\\main.cpp", __FUNCTION__ , 66 ));
(printf( "\033[" "33;40;1m" "[WARN]-[%s]-[%s]-[%d]:" "%d\n" "\033[0m", "D:\\repos\\C++Project\\main.cpp", __FUNCTION__ , 67,10));
(printf( "\033[" "31;40;1m" "[ERROR]-[%s]-[%s]-[%d]:" "%s\n" "\033[0m", "D:\\repos\\C++Project\\main.cpp", __FUNCTION__ , 68,"error"));
(printf( "\033[" "36;40;1m" "[DEBUG]-[%s]-[%s]-[%d]:" "Debug\n" "\033[0m", "D:\\repos\\C++Project\\main.cpp", __FUNCTION__ , 69 ));
(printf( "\033[" "36;40;1m" "[DEBUG]-[%s]-[%s]-[%d]:" "\033[0m", "D:\\repos\\C++Project\\main.cpp", __FUNCTION__ , 77 ));
*/
}

#@

char a = ToChar(1);               // a='1';
// char a = ToChar(123); // 编译器报错

__VA_ARGS__

  • 用于宏定义中代表可变参数
#define debug(...) printf(__VA_ARGS__)

c++20 初始化表达式

  • 使用c++11的range for的时候,就在好奇为什么没带有Initializationrange for,终于在C++20中见到了
for (Initialization ; traverse data)
{
// dosomething()
}

不能有const_cast<T>的原因

如果允许某种模板推导,它会更容易发生意外错误。其次const_cast也可以用来删除volatile,编译器怎么知道你想扔掉什么?

总结

在过去半年内,个人比较热爱C++的各种奇淫特性,内容更偏向笔记时所记录,所以本文更偏向简约不详细深入。不对某个特性进行深入总结,宗旨在抛砖引玉,简单地介绍特性的作用和用法,再通过后面的我觉得可以阅读的扩展阅读可进行深入了解。

TODO

  • C++进阶学习总结(二):

    • POD
    • CTAD和折叠表达式
    • type_traits
    • C++17一些值得了解的特性
    • 模板(SFINAE,std::enable_if(c++11),concept (c++20))

【C++】近期C++特性进阶学习总结(一)的更多相关文章

  1. Python进阶学习之特殊方法实例详析

    Python进阶学习之特殊方法实例详析 最近在学习python,学习到了一个之前没接触过的--特殊方法. 什么是特殊方法?当我们在设计一个类的时候,python中有一个用于初始化的方法$__init_ ...

  2. 爱了!阿里大神最佳总结“Flutter进阶学习笔记”,理论与实战

    前言 "小步快跑.快速迭代"的开发大环境下,"一套代码.多端运行"是很多开发团队的梦想,美团也一样.他们做了很多跨平台开发框架的尝试:React Native. ...

  3. PHP程序员进阶学习书籍参考指南

    PHP程序员进阶学习书籍参考指南 @heiyeluren lastmodify: 2016/2/18     [初阶](基础知识及入门)   01. <PHP与MySQL程序设计(第4版)> ...

  4. Matlab 进阶学习记录

    最近在看 Faster RCNN的Matlab code,发现很多matlab技巧,在此记录: 1. conf_proposal  =  proposal_config('image_means', ...

  5. zuul进阶学习(二)

    1. zuul进阶学习(二) 1.1. zuul对接apollo 1.1.1. Netflix Archaius 1.1.2. 定期拉 1.2. zuul生产管理实践 1.2.1. zuul网关参考部 ...

  6. ROS进阶学习笔记(11)- Turtlebot Navigation and SLAM - ROSMapModify - ROS地图修改

    ROS进阶学习笔记(11)- Turtlebot Navigation and SLAM - 2 - MapModify地图修改 We can use gmapping model to genera ...

  7. 代码走查25条疑问 C# 跳转新的标签页 C#线程处理 .Net 特性 attribute 学习 ----自定义特性 看懂 ,学会 .NET 事件的正确姿势-简单版

    代码走查25条疑问   代码走查(Code Review) 是一个开发人员与架构师集中讨论代码的过程.通过代码走查可以提高代码的 质量,同时减少Bug出现的几率.但是在小公司中并没有代码走查的过程在这 ...

  8. Struts2进阶学习4

    Struts2进阶学习4 自定义拦截器的使用 核心配置文件 <?xml version="1.0" encoding="UTF-8"?> <! ...

  9. Struts2进阶学习3

    Struts2进阶学习3 OGNL表达式与Struts2的整合 核心配置文件与页面 <?xml version="1.0" encoding="UTF-8" ...

随机推荐

  1. 三角网格上的寻路算法Part.1—Dijkstra算法

    背景 最近在研究中产生了这样的需求:在三角网格(Mesh)表示的地形图上给出两个点,求得这两个点之间的地面距离,这条距离又叫做"测地线距离(Geodesic)".计算三角网格模型表 ...

  2. hive 之 查看某库一共有多少张表

    思路一: show出所有表,然后wc -l hive -e" use database_name; show tables; "|wc -l 思路二: 1.show出当前库所有的表 ...

  3. Tool_Fiddler安装和使用

    一.简介 Fiddler(中文名称:小提琴)是一个HTTP的调试代理,以代理服务器的方式,监听系统的Http网络数据流动, Fiddler可以也可以让你检查所有的HTTP通讯,设置断点,以及Fiddl ...

  4. tomcat 服务器的几个重要监听 方法 与 使用

    1. 总结一下tomcat 服务器里的三种监听 ServletContextListener HttpSessionListener ServletRequestListener 这是我要做的三个自定 ...

  5. spring cloud --- config 配置中心 [本地、git获取配置文件]

    spring boot      1.5.9.RELEASE spring cloud    Dalston.SR1 1.前言 spring cloud config 配置中心是什么? 为了统一管理配 ...

  6. vue3.0获取地址栏参数

    方法一 toRaw(route).query.value 方法二 router.currentRoute.value.query

  7. Keil MDK STM32系列(六) 基于抽象外设库HAL的ADC模数转换

    Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...

  8. Cesium入门7 - Adding Terrain - 添加地形

    Cesium入门7 - Adding Terrain - 添加地形 Cesium中文网:http://cesiumcn.org/ | 国内快速访问:http://cesium.coinidea.com ...

  9. synergy最佳解决方案——barrier

    synergy最佳解决方案--barrier ​ 不知道大家有没有一套键盘鼠标控制多台电脑的需求,主流的硬件或说软件有大神整理如下: 软件方案: Windows 之间:Mouse Without Bo ...

  10. java-包概述

    1 package face_package; 2 3 import face_packagedemo.DemoA; 4 5 /* 包(package) 6 * 1,对类文件进行分类管理. 7 * 2 ...