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是指一个类A有一个基类,这个基类是类A本身的模板特化。具有编译时多态的特性
如下例子也可通过vtable实现。拿这个例子,将CRTP与vtable实现的动态多态进行对比
虚函数:
内存:每个虚函数一个函数指针而 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 fooAnotherDerived foo来自cppreference:CRTP
c++标准中对于CRTP的使用例子:std::enable_shared_from_this(cpp11)、std::ranges::view_interface(cpp20)
将资源的生命周期与对象的生命周期所绑定(构造获取资源/析构释放资源,利用了栈上的变量在离开作用域的时候会析构的特性),c++11后的四大smart_point(shared_ptr、unique_ptr、weak_ptr、auto_ptr(在17中废除))采用了这种思想。
一文带你了解智能指针(转载并结合总结)
typeid、dynamic_cast、type traits__RTtypeid,rtti把所需的type_info信息放在vtable前,大概也是dynamic_cast要求父类必须有虚函数的原因吧... 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及“反射”技术
metadata的能力,所以很多属性要靠手动注册,于是乎有人自造轮子搞了个反射机制(UE中的U++通过UHT和UBT来支持反射)Run Time Type Reflection
C++ Reflection Library
注意此处的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
将原有类型消除或者隐藏,换言之,在封装接口中,很多情况下我不关心具体类型是什么或者根本不需要这个类型,它可以使接口有更好的通用性、延展性,消除耦合,减少重复代码
类型擦除
只能说boost yyds啊,除了模板多,多次编译会导致编译时间长以外,功能真的很强大 确实如其名boost。例如c++17中的std::filesystem、std::any、std::varient直接来自于boost中。还有boost::program_options用于处理控制台的输入参数也是很方便
#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__)range for的时候,就在好奇为什么没带有Initialization的range for,终于在C++20中见到了for (Initialization ; traverse data){ // dosomething()}如果允许某种模板推导,它会更容易发生意外错误。其次const_cast也可以用来删除volatile,编译器怎么知道你想扔掉什么?
在过去半年内,个人比较热爱C++的各种奇淫特性,内容更偏向笔记时所记录,所以本文更偏向简约不详细深入。不对某个特性进行深入总结,宗旨在抛砖引玉,简单地介绍特性的作用和用法,再通过后面的我觉得可以阅读的扩展阅读可进行深入了解。