前言

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. 在CentOS7系统安装与配置RabbitMQ

    在CentOS7系统安装与配置RabbitMQ 远程访问需要开放端口 https://www.cnblogs.com/heqiuyong/p/10460150.html

  2. Shell自动上传下载文件到SFTP服务器

    1.说明 本文提供一个Shell脚本, 可以自动连接到SFTP服务器, 然后上传或者下载指定的文件, 进而可以使用Linux的corntab命令, 定时执行脚本上传下载文件, 实现文件的同步或者备份功 ...

  3. Win10 开启 Hyper-V 及简单使用

    简介 Windows 10 上内置了 Hyper-V.Hyper-V 提供硬件虚拟化,每个虚拟机都在虚拟硬件上运行. 系统要求 Windows 10 企业版.专业版或教育版.家庭版.移动版.移动企业版 ...

  4. 深入 Laravel 内核之装饰模式

    装饰模式核心内容: 装饰模式可以在不影响组件对象的情况下,以动态.透明的方式从外部给对象添加功能: 装饰器模式的本质就是动态组合.动态是手段,组合是目的.装饰模式是通过把复杂的功能简单化.分散化,在运 ...

  5. Flowable实战(一)启动第一个完整流程

    一.前言:   发现网上关于Flowable的资料基本都是浅尝辄止,对如何构建一个企业级的流程应用说明很少,所以写个实战系列,希望对大家和自己,都有所帮助. 二.认识Flowable   Flowab ...

  6. 51 Nod 1183 编辑距离 (动态规划基础)

    原题链接:1183 编辑距离 题目分析:这个最少的操作次数,通常被称之为编辑距离."编辑距离"一次本身具有最短的意思在里面.因为题目有"最短"这样的关键词,首先 ...

  7. 使用nginx访问FastDFS fastdfs nginx

    文中所有~~~均为同一个自定义文件夹名字,一般使用项目名称 2.1.为什么需要用Nginx访问? FastDFS通过Tracker服务器,将文件放在Storage服务器存储,但是同组存储服务器之间需要 ...

  8. Flowable实战(四)BPMN2.0 启动与结束事件

    一.BPMN2.0   BPMN2.0规范是一个标准,开源框架和不同供应商都遵循这份标准,使得最终用户不会因为依赖专有解决方案,而被供应商"绑架".有了BPMN2.0标准,不同解决 ...

  9. 【Java】Java8新特性

    文章目录 Java8新特性 Lambda表达式的使用 语法格式一:无参,无返回值 语法格式二:Lambda 需要一个参数,但是没有返回值. 语法格式三:数据类型可以省略,因为可由编译器推断得出,称为& ...

  10. javascript 获取<td>标签内的值。

    当网页被加载时,浏览器会创建页面的文档对象模型(Document Object Model). HTML DOM 模型被构造为对象的树. 通过可编程的对象模型,JavaScript 获得了足够的能力来 ...