C++ 侵入式智能指针实现原理


mutable 用于修饰类的成员变量,表示该成员变量可以在被标记为 const 的成员函数内修改。

1、const成员函数

在 C++ 中,将成员函数标记为 const 有两个主要目的:

  1. 约定和安全性: 使用 const 关键字是为了表达一个承诺,即该成员函数不会修改对象的状态,这增加了对于程序员来说的可读性。在 const 成员函数内部,编译器也会强制禁止修改非 mutable 的成员变量。

  2. 允许 const 对象调用: const 成员函数可以被 const 对象调用,而非 const 成员函数不能被const对象调用。

    • 假设有一个类 Person:

      class Person 
      {
      public:
          // 普通成员函数
          void setName(const std::string& newName) 
          {
              name = newName;
          }
          // const 成员函数
          std::string getName() const 
          {
              return name;
          }
      private:
          std::string name;
      };
      

      在这个例子中,setName 是一个普通的成员函数,而 getName 被标记为 const。现在,假设你创建了一个 常对象和一个普通对象,并用这两个对象调用普通成员函数SetName,很明显常量对象不能调用普通成员函数(这样做违反了 const 对象的语义,即不修改对象的外部状态。)

      const Person constPerson;
      Person nonConstPerson;
      
      constPerson.setName("Alice");  // 错误,const 对象不能调用非 const 成员函数
      nonConstPerson.setName("Bob");  // 正确
      

      尝试使用这两个对象调用常函数getName,都可以成功:

      
      std::cout << constPerson.getName() << std::endl;  // 正确,const 对象可以调用 const 成员函数
      std::cout << nonConstPerson.getName() << std::endl;  // 正确
      

2、使用mutable的场景举例

考虑这样的场景如何实现:有一个狗,它可以狗叫,并且记录狗叫次数,这个狗可以是const狗,也可以是非const狗

#include <iostream>

class Dog {
public:
	Dog() {}
	void Barking(){
		std::cout << "我在狗叫" << "\n";
		++count;
	}
	int GetBarkingCount() const {
		return count;
	}
private:
	int count = 0;
};

int main() 
{
	// 创建普通狗,并狗叫
	Dog nonConstDog;
	nonConstDog.Barking();
	nonConstDog.Barking();
	std::cout << "普通狗狗叫次数" << nonConstDog.GetBarkingCount() << "\n"; // 2次

	// 创建常量狗,并狗叫
	const Dog constDog;
	constDog.Barking(); // error 常量狗不能用普通狗叫,
	std::cout << "常量狗狗叫次数 " << constDog.GetBarkingCount() << "\n";

	return 0;
}

可以看到,如果这样写代码,常量狗是不配狗叫的,如果用上mutable,并且把狗叫函数改为const,就能很方便的实现了

class Dog {
public:
	Dog() {}
	void Barking() const {
		std::cout << "我在狗叫" << "\n";
		++count;
	}
	int GetBarkingCount() const {
		return count;
	}
private:
	mutable int count = 0;
};


lambda表达式中,如果没有为lambda指定mutable,则不能修改捕获变量的值。如果捕获的是一个对象,那么在函数体内部不能调用捕获对象的非const方法,因为非const方法是可能修改改对象的状态的

#include <iostream>

class Example 
{
public:
    void nonConstMethod() 
    {
        std::cout << "Non-const method called." << std::endl;
    }
};

int main() 
{
    Example obj;

    // 没有使用 mutable,尝试调用对象的非 const 方法会导致编译错误
    auto lambda = [obj]() 
    {
        // obj.nonConstMethod(); // 这一行会导致编译错误,因为默认情况下捕获的对象是 const 的
        std::cout << "Inside lambda." << std::endl;
    };

    lambda(); // 调用 lambda 表达式

    return 0;
}

所以这里必须给lambda表达式加一个mutable

auto lambda = [obj]() mutable
{
    obj.nonConstMethod();
    std::cout << "Inside lambda." << std::endl;
};

3、侵入式引用计数

std::shared_ptr是非侵入式引用计数,即使用的外部引用计数器。

一个基类,所有需要引用计数的类都需要继承自它:

class RefCounted
{
public:
	virtual ~RefCounted() = default;

	void IncRefCount() const
	{
		++m_RefCount;
	}
	void DecRefCount() const
	{
		--m_RefCount;
	}

	uint32_t GetRefCount() const { return m_RefCount.load(); }
private:
	mutable std::atomic<uint32_t> m_RefCount = 0;
};

自定义的智能指针类,仅仅是一个包装,内部维护了一个指针,其指向一个带引用计数的资源对象:mutable T* m_Instance

template<typename T>
class Ref
{
public:
	Ref() : m_Instance(nullptr) {}
	Ref(std::nullptr_t n) : m_Instance(nullptr)	{}
	Ref(T* instance) 
		: m_Instance(instance)
	{
		static_assert(std::is_base_of<RefCounted, T>::value, "Class is not RefCounted!");
		IncRef();
	}

	// 拷贝构造
	Ref(const Ref<T>& other)
		: m_Instance(other.m_Instance)
	{
		IncRef();
	}
	~Ref()
	{
		DecRef();
	}
	
	Ref& operator=(std::nullptr_t)
	{
		DecRef();
		m_Instance = nullptr;
		return *this;
	}
	
	Ref& operator=(const Ref<T>& other)
	{
		if (this == &other)
			return *this;

		other.IncRef();
		DecRef();

		m_Instance = other.m_Instance;
		return *this;
	}

	// 堆区创建T类型资源,把形参全部给他,并同时构造一个Ref<T>类型的"临时"对象返回
	// 完美转发
	template<typename... Args>	
	static Ref<T> Create(Args&&... args)
	{
		return Ref<T>(new T(std::forward<Args>(args)...));
	}
	
private:
	void IncRef() const
	{
		if (m_Instance)
		{
			m_Instance->IncRefCount();
			RefUtils::AddToLiveReferences((void*)m_Instance);
		}
	}

	void DecRef() const
	{
		if (m_Instance)
		{
			m_Instance->DecRefCount();
			
			if (m_Instance->GetRefCount() == 0)
			{
				delete m_Instance;
				m_Instance = nullptr;
			}
		}
	}
	mutable T* m_Instance;
};

使用方式:

class Image2D: public RefCounted
{
public:
	Image2D(std::string path)
	{
		// 加载并存储
	}
	
	// ......
}
int main() {
    // 引用计数增加到1
    Ref<Image2D> image1 = Ref<Image2D>::Create("path_to_image1.png");

    {
        // 引用计数增加到2
        Ref<Image2D> image2 = image1;
    } // image2 超出作用域,引用计数减少到1

   
    return 0;
} // image1 超出作用域,引用计数减少到0,Image2D 对象被销毁

  • 10
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
C++智能指针的原理和实现 智能指针的原理和实现 ⼀、智能指针起因 ⼀、智能指针起因   在C++中,动态内存的管理是由程序员⾃⼰申请和释放的,⽤⼀对运算符完成:new和delete。   new:在动态内存中为对象分配⼀块空间并返回⼀个指向该对象的指针;   delete:指向⼀个动态独享的指针,销毁对象,并释放与之关联的内存。   使⽤堆内存是⾮常频繁的操作,容易造成堆内存泄露、⼆次释放等问题,为了更加容易和更加安全的使⽤动态内存,C++11中引⼊了智 能指针的概念,⽅便管理堆内存,使得⾃动、异常安全的对象⽣存期管理可⾏。智能指针主要思想是RAII思想,"使⽤对象管理资源",在类 的构造函数中获取资源,在类的析构函数中释放资源。智能指针的⾏为类似常规指针,重要的区别是它负责⾃动释放所指向的对象。   RAII是Resource Acquisition Is Initialization的简称,即资源获取就是初始化:   1.定义⼀个类来封装资源的分配与释放;   2.构造函数中完成资源的分配及初始化;   3.析构函数中完成资源的清理,可以保证资源的正确初始化和释放;   4.如果对象是⽤声明的⽅在栈上创建局部对象,那么RAII机制就会正常⼯作,当离开作⽤域对象会⾃动销毁⽽调⽤析构函数释放资 源。 ⼆、智能指针类型 ⼆、智能指针类型   智能指针C++11版本之后提供,包含在头⽂件<memory>中,标准命名std空间下,有auto_ptr、shared_ptr、weak_ptr、unique_ptr四 种,其中auto_ptr已被弃⽤。   :拥有严格对象所有权语义的智能指针;   :拥有共享对象所有权语义的智能指针;   :到 shared_ptr 所管理对象的弱引⽤;   :拥有独有对象所有权语义的智能指针。 2.1 auto_ptr   auto_ptr是通过由 new 表达获得的对象,并在auto_ptr⾃⾝被销毁时删除该对象的智能指针,它可⽤于为动态分配的对象提供异常安 全、传递动态分配对象的所有权给函数和从函数返回动态分配的对象,是⼀个轻量级的智能指针,适合⽤来管理⽣命周期⽐较短或者不会被 远距离传递的动态对象,最好是局限于某个函数内部或者是某个类的内部。   声明:   template< class T > class auto_ptr;   template<> class auto_ptr<void>; // 对类型void特化     成员函数:   (1) : 获得内部对象的指针;   (2) :释放被管理对象的所有权,将内部指针置为空,返回内部对象的指针,此指针需要⼿动释放;   (3) :销毁内部对象并接受新的对象的所有权;   (4) :从另⼀auto_ptr转移所有权;   (5) 和:访问被管理对象。   注意事项:   (1) 其构造函数被声明为explicit,因此不能使⽤赋值运算符对其赋值,即不能使⽤类似这样的形 auto_ptr<int> p = new int;   (2) auto_ptr 的对象所有权是独占性的,使⽤拷贝构造和赋值操作符时,会造成对象所有权的转移,被拷贝对象在拷贝过程中被修改;   (3) 基于第⼆条,因此不能将auto_ptr放⼊到标准容器中或作为容器的成员;   (4) auto_ptr不能指向数组,释放时⽆法确定是数组指针还是普通指针;   (5) 不能把⼀个原⽣指针交给两个智能指针对象管理,对其它智能指针也是如此。   auto_ptr是最早期的智能指针,在C++11 中已被弃⽤,C++17 中移除,建议使⽤unique_ptr代替auto_ptr。   简单实现: 1 template<class T> 2 class AutoPointer 3 { 4 public: 5 AutoPointer(T* ptr) 6 :mPointer(ptr){} 7 8 AutoPointer(AutoPointer<T>& other) 9 { 10 mPointer= other.mPointer; //管理权进⾏转移 11 other.mPointer= NULL; 12 } 13 14 AutoPointer& operator = (AutoPointer<T>& other) 15 { 16 if(this != &other) 17 { 18 delete mPointer; 19 mPointer = other.mPointer; //管理权进⾏转移 20 other.mPointer= NULL; 21 } 22 23 return *this; 24 } 25 26 ~AutoPointer() 27 { 28 delete mP

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

宗浩多捞

您的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值