C++11新标准
友值引用、移动语义与完美转发
左值和友值之分
左值有地址,其变量名就是标识了一个物理地址。而右值是无名的。
左值引用和右值引用
引用是别名,就是给变量起另外的名字,都是标识同一个物理地址。左值引用即给左值起个别名,右值引用给右值取名,此时右值是变为了左值。
- 非const左值引用只能绑定到非const左值
- const左值引用可绑定到const左值、非const左值、const右值、非const右值
- 非const右值引用只能绑定到非const右值
- const右值引用可绑定到const右值和非const右值
移动语义与完美转发
万能引用(仅存在模板中)
-
引用折叠规则
所有的引用折叠最终都代表一个引用,要么是左值引用,要么是右值引用。如果任一引用为左值引用,则结果为左值引用。否则(即两个都是右值引用),结果为右值引用。
为什么传入左值,推导T为左值引用
首先,模板函数参数为 T&& param,也就是说,不管T是什么类型,T&&的最终结果必然是一个引用类型。如果T是int, 那么T&& 就是 int &&;如果T为 int &,那么 T &&(int& &&) 就是&,如果T为&&,那么T &&(&& &&) 就是&&。很明显,接受左值的话,T只能推导为int &。
-
万能引用T&&
由上面结论,传入左值,推导为左值引用,传入为右值,推导为右值引用。
移动语义
-
代码形式
template <typename T> typename remove_reference<T>::type&& move(T&& t) { return static_cast<typename remove_reference<T>::type&&>(t); }关键在于,强制转化为右值引用,而函数返回右值引用,是无名,所以是个右值。
-
使用
v.push_back(std::move(str));
完美转发
-
代码形式
template <typename T> T&& forward(typename std::remove_reference<T>::type& param) { return static_cast<T&&>(param); } template <typename T> T&& forward(typename std::remove_reference<T>::type&& param) { return static_cast<T&&>(param); } -
使用
template<typename T> void testForward(T && v){ print(std::forward<T>(v)); }关键在于,用的是输入T类型,而不是由函数来推导,如果原testForward函数接收为左值,那么经forward仍为左值,接收为右值,仍为右值。
RAII机制和智能指针
RAII机制
RAII全称是Resource Acquisition Is Initialization,翻译过来是资源获取即初始化,RAII机制用于管理资源的申请和释放。对于资源,我们通常经历三个过程,申请,使用,释放,这里的资源不仅仅是内存,也可以是文件描述符、socket、锁等等。但是我们往往只关注资源的申请和使用,而忘了释放,这不仅会导致内存泄漏,可能还会导致业务逻辑的错误,RAII就用来解决此类问题。
主要思想:抽象出管理资源类
- 构造函数中,对资源进行申请和初始化
- 抽象出资源使用接口
- 析构函数中,对资源进行释放
要使用资源,则初始化对象,对象在离开作用域后,就会自动回收资源。
关于锁的RAII机制设计(MYWEB项目)
class Locker
{
public:
Locker()//申请资源
{
if (pthread_mutex_init(&Mitex, NULL) != 0)
{
throw std::exception();
}
}
~Locker()//释放资源
{
pthread_mutex_destroy(&Mitex);
}
bool Lock()//上锁接口
{
return pthread_mutex_lock(&Mitex) == 0;
}
bool Unlock()//解锁接口
{
return pthread_mutex_unlock(&Mitex) == 0;
}
pthread_mutex_t *get()
{
return &Mitex;
}
private:
pthread_mutex_t Mitex;
};
智能指针
为什么要有智能指针?
因为在平时编程过程中,极大可能会出现两种情况;
(1)malloc出来的空间,没有进行释放,存在内存泄漏;
(2)如果在malloc和free之间抛异常,那么同样存在内存泄漏问题。该问题称为异常安全问题。
使用智能指针可以解决跳出函数要频繁delete的问题,保证内存不泄漏,且通过析构函数来释放资源,使访问内存更加安全。
简单智能指针类
//智能指针的简单实现
template<class T>
class SmartPtr
{
public:
SmartPtr(T*ptr = nullptr)
:_ptr(ptr)
{
}
~SmartPtr()
{
if (_ptr)
{
delete _ptr;
}
}
T& operator*()
{
return *_ptr;
}
T* operator->()//返回指向空间的内容
{
return _ptr;
}
private:
T* _ptr;
};
//使用方式
void MergeSort(int *a, int n)
{
int*tmp = (int*)malloc(sizeof(int)*n);
SmartPtr<int> sp(tmp);//把tmp指针委托给sp对象
}
//主要就是体现了智能指针可以构造时初始化,析构时释放资源
shared_ptr
-
std::shared_ptr是一种智能指针,它能够记录多少个shared_ptr共同指向一个对象,从而消除显式的调用delete,当引用计数变为零的时候就会将对象自动删除。- 创建对对象的引用,初始化指针并将引用计数置为1
- 当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数(这里对象是指智能指针)(因为当前左指针可能指向某一个对象,现在进行切换,肯定做一个减法)
- 对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数。
- 调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除基础对象)
-
代码实现
template<typename T> class SharedPtr { //默认构造函数 SharedPtr(T* ptr=nullptr):ptrcount(new int(1)),ptr(ptr) { } //拷贝构造 SharedPtr(const SharedPtr& s):ptrcount(s.ptrcount),ptr(s.ptr) { (*ptrcount_)++; } //赋值运算符 左操作数--因为减少了对原对象的引用 右操作数++ 因为增加了引用 SharedPtr& operator=(const SharedPtr& s) { //如果不是指向同一对象,即发生指向对象切换 if(this!=s) { if(--*(ptrcount)==0) { delete ptrcount; delete ptr; } //右操作数指向对象引用增加 ptrcount=s.ptrcount; ptr=s.ptr; (*ptrcount)++; } return *this; } T& operator*() { return *ptr; } T* operator->() { return ptr; } ~SharedPtr() { --(*ptrcount); if(*ptrcount==0) { delete ptrcount; //释放计数器 ptrcount=nullptr; delete ptr; //释放对象 ptr=nullptr; } } private: int* ptrcount;//计数 之所以用指针,因为计数对象是指向同一对象的智能指针共用的 T* ptr; //指向对象。 }
循环引用?
环形链表使用智能指针,引用成环,两个指针指向的内存都无法释放。
class ClassB;
class ClassA
{
public:
ClassA() { cout << "ClassA Constructor..." << endl; }
~ClassA() { cout << "ClassA Destructor..." << endl; }
shared_ptr<ClassB> pb; // 在A中引用B
};
class ClassB
{
public:
ClassB() { cout << "ClassB Constructor..." << endl; }
~ClassB() { cout << "ClassB Destructor..." << endl; }
shared_ptr<ClassA> pa; // 在B中引用A
};
int main() {
shared_ptr<ClassA> spa = make_shared<ClassA>();
shared_ptr<ClassB> spb = make_shared<ClassB>();
spa->pb = spb;
spb->pa = spa;
// 函数结束,思考一下:spa和spb会释放资源么?
}
unique_ptr
unique_ptr采用的是一种独占的智能指针,它禁止其他智能指针与其共享同一个对象。那么只设置默认初始化即可。
weak_ptr
引用计数有一个问题就是互相引用形成环(环形引用),这样两个指针指向的内存都无法释放。结合 shared_ptr 使用的特例智能指针。 weak_ptr 提供对一个或多个 shared_ptr 实例拥有的对象的访问,但不参与引用计数。
-
特性
- 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少。
- weak_ptr并没有重载operator->和operator *操作符,因此不可直接通过weak_ptr使用对象,典型的用法是调用其lock函数来获得shared_ptr示例,进而访问原始对象。
-
使用
- 对于不想增加引用数的指针,用弱指针
- 因为没有*和->运算符,当需要访问时,通过lock函数来获取shared_ptr示例
class ClassB; class ClassA { public: ClassA() { cout << "ClassA Constructor..." << endl; } ~ClassA() { cout << "ClassA Destructor..." << endl; } weak_ptr<ClassB> pb; // 在A中引用B }; class ClassB { public: ClassB() { cout << "ClassB Constructor..." << endl; } ~ClassB() { cout << "ClassB Destructor..." << endl; } weak_ptr<ClassA> pa; // 在B中引用A }; int main() { shared_ptr<ClassA> spa = make_shared<ClassA>(); shared_ptr<ClassB> spb = make_shared<ClassB>(); spa->pb = spb; spb->pa = spa; // 函数结束,思考一下:spa和spb会释放资源么? }
auto_ptr(c98标准)
auto_ptr 是c++ 98定义的智能指针模板,其定义了管理指针的对象,可以将new 获得(直接或间接)的地址赋给这种对象。当对象过期时,其析构函数将使用delete 来释放内存!
auto_ptr 被C++11抛弃的主要原因
- 复制或者赋值都会改变资源的所有权
- 在STL容器中使用auto_ptr存在着重大风险,因为容器内的元素必须支持可复制和可赋值
- 不支持对象数组的内存管理

浙公网安备 33010602011771号