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存在着重大风险,因为容器内的元素必须支持可复制和可赋值
  • 不支持对象数组的内存管理
posted @ 2022-07-14 20:30  xfw121  阅读(81)  评论(0)    收藏  举报