一、创建C++类后,编译时会自动生成default构造函数、copy构造函数、赋值操作符、析构函数。
定义类时,请明确定义上述四个函数的性质:
clsA() = default; //明确告诉编译器使用默认生成的构造函数
clsA() = delete; //明确告诉编译器禁止使用构造函数
virtual ~clsA()=0;//明确告诉编译器为纯虚函数(C98版需要加{})
void opr2() final; //明确告诉编译器该函数不可被继承
void opr1() override; //明确告诉编译器该函数完全继承父类的定义
二、C++关键字explicit只能写在在声明中,不能写在定义中。
1、用于类型转换函数,例:explicit operator double() const { return d; }
调用时必须声明显式转换,static_cast<double>(f);
2、用于单操作数构造函数,则禁止构造函数的隐式转换。例:explicit A(const B& b) {}
3、用于拷贝构造函数,则这个类对象不能隐式调用,用于传参传递和函数返回值。同时禁止编译器自动调用拷贝初始化,还可以禁止编译器对拷贝函数的参数进行隐式转换。例:
explicit A(const A& a) {} //无法使用RVO返回值优化
三、显式RVO,显式NRVO,浅拷贝,深拷贝
A&& getVar() { return A(); } //浅拷贝,报警,返回局部变量的引用
A& getVar() { return A(); } //浅拷贝,报警,返回局部变量的引用
A getVar() { return A(); }//深拷贝,会进行显式RVO优化。
!!! 除类成员对象,任何时候不要返回引用,无论左值右值。
四、右值引用、拷贝构造、移动构造、拷贝赋值、移动赋值
1、移动构造是需要通过移动构造函数std::move来完成的。!!! 所以不建议使用移动构造,默认构造+RVO非常完美。
2、移动赋值,像下图这样使用:
void operator=(clsA&& a) noexcept { m_int = a.m_int; a.m_int = nullptr; }
//这样a对象在析构销毁时,因为指针为nullptr。而clsA自己m_int实际接管了a.m_int的指针
五、关于网上消息的谬论,不知道为啥这些未经验证就能发出来,C++本就复杂了,再鱼龙混杂,其心叵测。
1、explicit只能用于构造函数中。
2、int &&k = getVar(); //第二行语句中getVar()产生的临时对象不会像第一行代码那样,在表达式结束之后就销毁了,而是会被“续命”,它的生命周期将会通过右值引用得以延续,和变量k的生命周期一样长。
经测试,经编译器RVO优化没有临时对象,哪来的生命周期?而直接返回右值引用的临时对象,生命周期也是创建后复制传递后就销毁。
总结:C++的内存管理机制RAII已经很好用了,新增的右值引用功能有限(仅用于移动赋值),C++11改进更像是语法糖。但增加与编译器的沟通是好事,明确声明才会减少内存问题的发生和产生岐义。后面C++融入更多机制(所谓Modern C++),也只是便于管理、融合标准库、增强多线程的支持。