c++11/c++14新特性
转向现代C++
特种成员函数的生成机制
特种成员函数有
- 默认构造函数,析构函数,复制构造函数,复制赋值运算符
- c++11新增了,移动构造函数和移动赋值运算符
一些特性和区别
- 移动构造函数和移动赋值运算符,只要声明了其中一个,那么就会阻止另一个的生成
- 一旦显式声明了复制操作,类就不会在生成移动操作
- 如果显式声明了移动操作,复制操作将会被废弃
- 超出对象的作用范围对象自动析构,防止内存泄漏
- 移动操作的生成条件包括,该类没有声明任何赋值操作,未声明任何移操作,未声明任何析构函数
智能指针
std::unique_ptr
- 是个移动型别,不允许复制
- 对托管资源有专属所以权
- 随着管理对象的析构而被析构
- 访问管理对象的内容,可以通过 -> 提领操作;访问指向的内容可以用 *****提领操作
- 能够自定义析构器,并且自定义析构器作为std::unique_ptr型别的一部分
auto delInv = [](Investment* p){
log(p);
delete p;
}
auto makeInv(){
std::unique_ptr<Investment, decltype(delInv)> pInv(nullptr, delInv);
return pInv;
}
- 能够简便地转换为share_ptr
auto p = makeInv();
std::share_ptr<Investment> sp = p;
- 如何使用
class Test{
public :
int id;
Test():id(3){}
~Test(){cout << "object deleted";}
};
void uniqueTest(){
auto up1 = std::make_unique<Test>();
cout << "befor handing out the power" << endl;
cout << "up1 id " << up1->id << endl;
auto up2 = std::move(up1);
cout << "handed out the power ";
cout << "up2 id " << up2->id << endl;
cout << "up1 id " << up1->id << endl;//因为对象的所有权已经移交给了up2,出错
}
int main()
{
uniqueTest();
return 0;
}
程序运行结果
- 当类中有std::unique_ptr类型的数据成员时,也要进行深拷贝
class Widget{
public:
Widget();
~Widget();
Widget(const Widget& rhs);
Widget& operator=(const Widget& rhs);
private:
struct Impl;
std::unique_ptr<Impl> pImpl;
}
//.cpp
Widget::Widget(const Widget& rhs):pImpl(std::make_unique<Impl>(*rhs.pImpl)){}//如果进行浅拷贝,那么被复制的指针将失去对原来对象的所有权
Widget& Widget::operator=(const Widget& rhs)
{
*pImpl = *rhs.pImpl;
return *this;
}
std::shared_ptr
- 共享所有权,通过引用计数来管理对象,引用计数为0时,对象被析构
- 可以自定义析构器,但是不作为其型别的一部分
auto del = [](Widget* p){
log(p);
delete p;
}
auto makeInv(){
std::shared_ptr<Widget> spw(nullptr, del);
return spw;
}
- 创建要注意避免用裸指针型别的变量来创建std::share_ptr
//用变量创建的后果是,用同一个对象创建两个std::share_ptr时会导致,当其中一个引用计数为0时,w会被析构两次,第二次析构会引发未定义行为
auto w = new Widget;
std::shared_ptr<Widget> spw1(w,del);
std::shared_ptr<Widget> spw2(w,del);
//常用的创建方式
std::shared_ptr<Wideget> spw(new Widget, del);
std::weak_ptr
- 是std::share_ptr的一种扩充,替代悬空的std::shared_ptr,一般通过std::share_ptr创建
auto spw = std::make_shared<Widget>();
std::weak_ptr<Widget> wpw(spw);
- 无法进行提领操作
- 检测std::weak_ptr是否悬空(失效),用原子操作完成
std::shared_ptr<Widget> spw1 = wpw.lock();
//或者
auto spw2 = wpw.lock();
- 应用场景
- 缓存
- 观察者列表
- 避免std::shared_ptr指针回路
std::make_unique 和std::make_shared
- 优先选用这两者对智能指针进行初始化,而非new
- 消除了代码重复,提高了异常安全性
- make系列函数不适用的场景
- 需要自定义析构器的场景
- 传递大括号初始化物的场景
void proWidget(std::shared_ptr<Widget> spw, int pri);
void del(Wideget *);//自定义析构器
proWidget(std::shared<Widget>(new Widget, del), computPri());//有潜在的资源泄漏风险,原因是computPri可能发生在Widget构造函数生成对象之后在把对象交给智能指针之前,如果computPri中途发生异常,Widget对象就无法交给std::shared_ptr,从而内存泄漏
//version_1:为了提高安全性,采用make系列函数
proWidget(std::make_shared<Widget>(), computPri());
//version_2:但是用make系列函数不能用自定义析构器,可以在保证安全性的前提下,可以采用以下方案
std::shared<Widget> spw(new Widget, del);
proWidget(spw, computPri());
//version_3:因为proWidget是按值传递的,右值只需移动一次,而且不会对引用计数有任何的操作,但左值需要进行一次复制,会对引用计数进行递增操作,而且效率低,为此还可以进行以下改进
std::shared<Widget> spw(new Widget, del);
proWidget(std::move(spw), computPri());//高效又安全
使用Pimpl的习惯用法时,把特殊成员函数的定义放到实现文件(.cpp)中
- 什么是Pimpl
class Widget{
public:
Widget();
~Widget();
private:
std::string name;
std::vector<double> data;
Gadget g1, g2, g3;
}
//Pimpl
class Widget{
public:
Widget();
~Widget();
private:
struct Impl;
std::unique_ptr<Impl> pImpl;
}
//.cpp
struct Widget::Impl{
std::string name;
std::vector<double> data;
Gadget g1, g2, g3;
};
- Pimpl 可以提升编译的速度
lambda表达式
避免默认捕获
- 按引用的默认捕获会导致悬空指针问题
- 按值的默认捕获会容易受悬空指针的影响
对auto&& 型别的形参使用decltype,以std::forward之
auto f = [](auto&& param){
return normalize(std::forward<decltype(param)>(param));//decltype得到param的原始型别std::forward保持保持左值或右值性质不变
}
右值引用,移动语意,完美转发
std::move和std::forward
- 两者的实现原理其实是实行了强制转换
- 两者在运行期不会做任何的操作
- std::move实现的是将左值转换为右指,如果要取得对某个对象执行移动操作能力,则不要将其声明为常量,否则会
class A{
string value;
public:
A(const string s):value(std::move(s)){}//将s复制到了value中而非移动
A(string s):value(std::move(s)){}//移动
};
- std::forward实现的是转发,保留转发变量的左值和右值性质
auto f = [](auto&& x){
return std::forward<decltype(x)>(x);
};
区分万能引用和右值引用
- 万能引用的特征是 T&& param 或者auto && param,&&前只有模板参数T(T只是个型别名称)涉及型别推导
- 特殊情况
//这种情况下不是万能引用,因为不具备性别推导,vector对象的型别完全决定了,push_back的声明型别
template<class T>
class vector{
public:
void push_back(T&& x);
};
针对右值引用实施std::move,针对万能引用实施std::forward
- 针对右值引用的最后一次使用实施std::move,针对万能引用的最后一次引用实施std::forward
void setText(Text&& text){
...
add(std::move(text))
}
void setText(auto&& text){
...
add(std::forward<decltype(text)>(text));
}
- 按值返回的函数的右值引用和万能引用采取和上一条相同的原则
Widget f1(Widget&& w){
return std::move(w);
}
Widget f2(auto&& w){
return std::forward<decltype(w)>(w);
}
- RVO(返回值优化),成立条件是局部对象型别和返回值型别相同且返回的是局部对象本身
Widget getWidget(){//编译器默认优化,w是被移动到返回值的内存里的而不是复制,因此这种情况下不要用std::move或者std::forward
Widget w;
return w;
}