C\C++_指针_智能指针模板类

文章目录

  • 1. 模板种类
  • 2. 智能指针使用示例
    • 2.1 智能指针初始化
      • 2.1.1 shared_ptr的初始化
      • 2.1.2 unique_ptr的初始化
    • 2.2 智能指针移交所有权
      • 2.2.1 unique_ptr移交所有权
  • 3. 有关智能指针的注意事项
  • 4. unique_ptr优于auto_ptr
  • 5. unique_ptr转为shared_ptr
  • 6. 参考书籍
  • 7. 智能指针指定删除器
    • 7.1 shared_ptr
    • 7.1.1 方式一、使用普通函数
    • 7.1.2 方式二、使用仿函数
    • 7.1.3 方式三、使用lambda表达式
    • 7.1.4 方式四、使用std::default_delete
    • 7.2 unique_ptr
      • 7.2.1 普通函数
  • 8. 规避问题
    • 8.1 以独立的语句将newed对象置入智能指针

1. 模板种类

智能指针模板类 支持情况 使用场景
auto_ptr C++98提供的,C++11已摒弃,但如果编译器不支持其它两种C++11提供的,则只能使用auto_ptr
unique_ptr C++11提供 (如果编译器没提供,可使用Boost库的scoped_ptr) 1. 不需要多个指向同一对象的指针
2. 支持下标法操作内部指针指向的元素
shared_ptr C++11 提供(如果编译器没提供,可使用Boost库的shared_ptr) 2. 多个指针指向同一个对象,支持复制和赋值操作
2.不支持下标法访问内部指针指向的元素> 注意:需要包含头文件

2. 智能指针使用示例

#include 
#include 
#include 

class CReport
{
private:
    std::string m_str;

public:
    CReport(const std::string &str)
        :m_str(str)
    {
       std::cout << "Object created!!!\n"; 
    }
    ~CReport()
    {
        std::cout << "Object deleted!!!\n";
    }
    void Show() const
    {
        std::cout << m_str << "\n";
    }
};

void Test01()
{
    //! 都是模板类,本质就是模板类中维护一个T*指针,通过析构函数来delete T*指针
    std::auto_ptr<CReport> pa(new CReport("using auto_ptr"));   
    pa->Show();     

     std::shared_ptr<CReport> ps(new CReport("using shared_ptr"));   
    ps->Show();     

    std::unique_ptr<CReport> pu(new CReport("using unique_ptr"));   
    pu->Show();     
}

2.1 智能指针初始化

2.1.1 shared_ptr的初始化

如果我们不初始化智能指针,它就会被初始化为一个空指针。

  • 使用make_shared函数
    #include
		std::shared_ptr<std::string> spStr = std::make_shared<std::string>("harry");
		//! spStr用作一个条件判断,若spStr指向一个对象,则为true
		if (spStr && spStr->empty())	
		{
			*spStr = "hello world";
		}
		
		//! 也可以使用auto来简化变量的声明
		auto p6 = std::make_shared<std::string>("hello");
		std::cout << *p6 << std::endl;

使用make_shared分配内存失败会抛出异常,示例如下:

	std::shared_ptr<std::string> spStr;
	try
	{
		spThrd = std::make_shared<std::string>();
	}
	catch (std::bad_alloc& ex)
	{
		cat.info("[%s] 初始化失败,详情:%s", TESTITEM_NAME, ex.what());
		return false;
	}
	catch (...)
	{
		cat.info("[%s] 初始化失败,未知错误", TESTITEM_NAME);
		return false;
	}	

在类的构造函数初始化列表中初始化智能指针

class CStudent
	{
	public:
		CStudent(int a)
			:m_sp(std::make_shared<int>(a)){}
		int Get() const {return *m_sp;}

	private:
		std::shared_ptr<int> m_sp;
	};

	void Test01()
	{
		CStudent s(3);
		std::cout << s.Get();
	}
  • 使用构造函数初始化
//! 由于智能指针接受指针参数的构造函数是explicit,所以无法隐士转换。
shared_ptr<int> p1 = new int(1024);	//< 错误,不支持隐士转换
shared_ptr<int> p2(new int(42));	//< 正确	
  • reset函数
    通常和unique一起使用,来控制多个shared_ptr共享的对象。在改变底层对象之前,我们检查自己是否当前对象仅有的用户,如果不是,在改变之前要制作一份新的拷贝,避免更改了其它用户的内容
auto p = std::make_shared<int>(5);
		auto q(p);
		if (!p.unique())	//< 判断是否唯一的用户(引用计数大于1),如果不是则重新分配新的拷贝
		{
			p.reset(new int(*p));	//< 重新分配新的拷贝,这个时候p所管理的内存就是自己独有的了,可以随意改变,不用担心影响其它用户
		}
		*p = 15;
		

2.1.2 unique_ptr的初始化

unique_ptr<string> p1(new string("hello"));

//! 将所有权从p1(指向"hello")转移给p2
unique_ptr<string> p2(p1.release());	//< release将p1置为空

2.2 智能指针移交所有权

2.2.1 unique_ptr移交所有权

unique_ptr<string> p1(new string("world"));
unique_ptr<string> p2(new string("harry"));
//! 将所有权从p1转移给p2,release返回之p1内部所管理对象的指针,并将p1内部的指针置为空
p2.reset(p1.release()); //<	reset释放了p2原来指向的内存

3. 有关智能指针的注意事项

  1. 三种智能指针都应避免
std::string str ("hello");
shared_ptr<std::string> pstr(&str);	//< 不允许
// 因为pstr过期时, 析构会delete对象内部维护的std::string指针,而这里str是栈上的内存,不是堆空间的,这里将把delete运算符用于非堆内存,非法操作
  1. 三种智能指针对赋值语句的策略
  • auto_ptr
 	std::auto_ptr<std::string> pa(new std::string("hello world"));
    std::auto_ptr<std::string> pa1;
    pa1 = pa;       				//< 赋值的时候,移交了所有权给pa1
    std::cout <<* pa.get();         //< 异常,因为pa交出了所有权,所以pa维护的std::string指针为空,导致以打印的时候会出错
    std:: cout << *pa1.get();       //< 正常
  • shared_ptr
   	std::shared_ptr<std::string> pa(new std::string("hello world"));    //< 引用计数+1
    std::shared_ptr<std::string> pa1;   //< 引用计数+1
    pa1 = pa;           				//< 内部维护的std::string指针指向同一个堆空间
    std::cout <<*pa.get() << std::endl;          //< 正常,没有所有权的概念,采用引用计数的方式,
                                                 //< 只有引用计数降为0的时候,析构才delete所维护的std::string指针
    std:: cout << *pa1.get();                    //< 正常
  • unique_ptr
 	//! unique_ptr也是使用所有权模型,与auto_ptr一样,但unique_ptr赋值会在编译阶段就报错
    std::unique_ptr<std::string> pa(new std::string("hello world"));    
    std::unique_ptr<std::string> pa1; 
    pa1 = pa;           //< 这里在编译阶段就报错
    std::cout <<*pa.get() << std::endl;                            
    std:: cout << *pa1.get();       
  1. C++ Primer 12.1.4 智能指针和异常
    C\C++_指针_智能指针模板类_第1张图片

4. unique_ptr优于auto_ptr

  • 编译器阶段错误比潜在的程序崩溃更安全
    std::unique_ptr<std::string> pa(new std::string("hello world"));    
    std::unique_ptr<std::string> pa1; 
    pa1 = pa;           //< 这里在编译阶段就报错,但如果使用auto_ptr,则编译不会报错,
    					//<	使用pa内部维护的std::string指针时才会出错
  • 允许赋值的情况
    程序试图将一个unique_ptr赋给另一个时,如果源unique_ptr是个临时右值,编译器允许这样做;但如果源unique_ptr将存在一段时间,编译器将禁止这样做。如下代码示例:
 std::unique_ptr<std::string> pu(new std::string("hello world"));
 std::unique_ptr<std::string> pu1;
 // pu1 = pu;             //< 编译器禁止这么做,因为pu长期存在,可能会存在使pu中的std::string对象的情况

 std::unique_ptr<std::string> pu3;
 pu3 = std::unique_ptr<std::string>(new std::string("hello world")); //< 允许,因为右边生成的临时对象,再把std::string对象的所有权移交给pu3后,会很快被销毁,没有机会使用它来访问无效的数据

//! 函数的返回值,也是临时右值,可以这么使用,当把临时右值赋值给接收者变量std::unique_ptr后,
//! 转移了所有权,临时右值会被销毁
std::unique_ptr<std::string> Get()
{
	std::unique_ptr<std::string> pu(new std::string("hello"));
	return pu;
}

  • 可用于数组的变体
    C\C++_指针_智能指针模板类_第2张图片
    使用new分配内存——可以使用auto_ptr、shared_ptr和unique_ptr
    使用new[]分配内存——可以使用unique_ptr

5. unique_ptr转为shared_ptr

模板shared_ptr包含一个显示构造函数,可将右值unique_ptr转换为shared_ptr,share_ptr将接管原来归unique_ptr所有的对象

std::unique_ptr<int> GetUni(int n)
{
    return std::unique_ptr<int>(new int(n));
}

int main()
{
    std::unique_ptr<int> pu(GetUni(3));     //< 正确,pu构造函数内为临时的unique_ptr对象
    std::shared_ptr<int> ps(pu);                  //< 报错,编译器不允许右值为非临时变量
    std::shared_ptr<int> puts(GetUni(3));  //<  正确,将临时的unique_ptr对象转为shared_ptr对象
    
	return 0;
}

6. 参考书籍

C++ Primer Plus(第6版)——16.2 智能指针模板类

7. 智能指针指定删除器

7.1 shared_ptr

将回调函数传递给 shared_ptr 的构造函数,该构造函数将从其析构函数中调用以进行删除

7.1.1 方式一、使用普通函数

// 自定义删除器
void deleter(Sample * x)
{
	std::cout << "Deleter function called" << std::endl;
	delete[] x;
}
// 构造函数传递自定义删除器指针
std::shared_ptr<Sample> p1(new Sample[5], deleter);

7.1.2 方式二、使用仿函数

class Deleter
{
public:
	void operator() (Sample *x) {
		std::cout << "Deleter function called" << std::endl;
		delete[] x;
	}
};
// 构造函数传递自定义删除器指针
std::shared_ptr<Sample> p2(new Sample[5], Deleter);

7.1.3 方式三、使用lambda表达式

std::shared_ptr<Sample> p3(new Sample[5], [](Sample *x) {
	std::cout << "Deleter function called" << std::endl;
	delete[] x;
});

7.1.4 方式四、使用std::default_delete

std::shared_ptr<Sample> p(new Sample[5], std::default_delete<Sample[]>());

7.2 unique_ptr

7.2.1 普通函数

需要使用decltype关键字

void Deleter(std::string* pstr)
	{
		delete pstr;
		pstr = nullptr;
	}

	void Test01()
	{
	//! 
		std::unique_ptr<std::string, decltype(Deleter)*> up(new std::string("hello"), Deleter);
	}

8. 规避问题

8.1 以独立的语句将newed对象置入智能指针

effective c++ 条款17 (第三版)

//! 假如如下函数
int priority();
void processWidget(std::shared_ptr<Widget> pw, int priority);

//! 调用
processWidget(std::shared_ptr<Widget>(new Widget), priority());
  • 现象:编译器产出一个processWidget调用码之前,必须首先核算即将被传递的各个实参。也就是会做以下三件事:调用priority(), 执行new Widget, 调用std::shared_ptr构造函数。
  • 分析:但c++ 编译器对参数核算的顺序弹性很大,不确定哪个在前哪个在后。所以可能会产生1. 执行new widget;2. 调用priority();3. 调用std::shared_ptr()构造函数,假如priority调用导致异常,则new Widget返回的指针将会遗失,因为它尚未被置入std::shared_ptr内,所以导致资源泄漏。
  • 解决措施:
    使用分离语句,分别写出(1)创建Widget, (2)将它置入一个只能指针内,再把那个智能指针传给processWidget函数,如下:
std::shared_ptr<Widget> pw(new Widget); //< 在单独语句内以只能指针存储newed所得的对象
processWidget(pw, priority());	//< 这个调用动作绝不至于造成泄漏

以上之所以可以,因为编译器对于“跨越语句的各项操作”没有重新排列的自由(只有在语句内它才拥有那个自由度)

你可能感兴趣的:(C\C++,c++,c语言)