C++常用类 shared_ptr

本文探讨了C++中`shared_ptr`的使用,强调了资源管理的重要性,特别是在异常处理中避免内存泄漏。文章还讨论了`unCopyable`类的赋值操作符改进,提倡使用`copy-and-swap`技术。此外,解释了C++的隐式转换、`explicit`关键字的作用,并提到了对象管理资源以确保在异常情况下正确释放。最后,指出了`shared_ptr`在复制和赋值时如何处理引用计数,以及定义类时需要考虑的对象切割、传值与传引用以及非局部静态对象交互的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

unCopyable类

class unCopyable{
protected:
unCopyable(){}
~unCopyable(){}
private:
 unCopyable(const unCopyable &);
unCopyable & operator=(const unCopyable&);
}
class home :private unCopyable{}

注意上面的基类的protected类型的构造函数和析构函数,子类是private继承。private继承是实现继承。

赋值操作符:

widget & widget::operator=(const  widget& rh){

 if(this == &rh)

return *this;

delete pb;

pb = new Bitmap(*rh.pb);

reruen *this;

}

这种函数虽然考虑了两者相同的情况,但是没有考虑当new操作发生异常时 的情况。当异常是,pb会指向已经释放的内存,导致不确定行为。

更好的方式是

widget & widget::operator=(const  widget& rh){

Bitmap* porig = pb;

pb = new Bitmao(*rh.pb);

delete porig;

return *this;

}

这里即使两者相同,也不会有问题,只不过多做了一次拷贝,对效率有影响。我们可以考虑将判断语句加进去,但是判断语句会使代码变大,产生新的分支流,这两点都会降低CPU的效率。所以我们应该考虑自我赋值的发生频率有多高。

还有一个替代方案是copyandswap技术:

class widget{

private:

void swap(const widget&rh){}

public:

widget & widget::operator=(const  widget& rh){

widget temp (rh);

swap(temp);

return *this;

}

C++定义隐式转换函数

  C++中单参数构造函数若不声明为explict,在合适的场合可以产生隐式转换:由成员变量类型转换为类类型。

  下面的代码展示如何实现反向的转换: 

void changeFontSize(FontHandle f, int newsize);
class Font{
public:
    operator FontHandle() const   //隐式转换函数
    {
        return f;
    }
private:
    FontHandle f;
};

Font f;
int newsize;
changeFontSize(f, newsize);      //将Font隐式转换成FontHandle


 

   另外,两个或更多参数的non-explicit构造函数,如果所有形参都提供了默认实参,那么在需要一个类类型对象的表达式位置,提供一个first形参类型的对象,编译器也执行隐式转换,转换得到一个类对象。

  如,构造函数Rational(int num1 = 0,  int num2 = 1); Rational类中重载了operator*。表达式result = oneHalf * 2; 2发生隐式转换为Rational对象。result 和 onehalf为Rational对象。


隐式转换函数:

class Font{

private: 

FontHandle f;

public :

operator FontHandle() const{//隐式转换

return f;

}

}

void changeFrontSize(FontHandle f ,int size){}

Font font;

int size;

当以Font对象调用changeFrontSize函数时,便会执行隐式转换函数,这样

changeFrontSize(font,size);

用对象管理资源的原因:

因为异常会导致函数未执行完提前返回,所以我们正常的在函数末尾释放资源会因为异常的发生造成内存泄露。所以要才有对象管理资源,当对象离开作用域时,会自动执行其析构函数,这样就会保证资源的释放。

ecplicit关键字

隐式转换可以用explict关键字防止。其主要是防止一个类型的对象转换出另一个新的类型的新对象。

默认构造函数是用于构造对象的,这两个没什么太大关系,将默认构造函数声明为explicit可以防止参数类型对象生成一个类类型的临时对象。


关于C++的shared_ptr,只有在其通过复制构造函数和赋值操作符时,两者的引用计数才和对象持有者相关。从以下两个例子可以看出来:

# include <iostream>
# include <tr1/memory>
using namespace std;
class A {
public:
    A() {
        cout << "construct A!!!" << endl;
    }
    ;
    ~A() {
        cout << "destruct A!!!" << endl;
    }
    ;
};
class B: public A {
public:
    B() {
        cout << "construct B!!!" << endl;
    }
    ;
    ~B() {
        cout << "destruct B!!!" << endl;
    }
    ;
};
int main() {
//  B* ptrB0 = new B();
    B * pint = new B();
    std::tr1::shared_ptr<B> ptrB1(pint);

    std::tr1::shared_ptr<B> ptrB2(ptrB1);

    std::tr1::shared_ptr<B> ptrB3(ptrB1);
    cout << ptrB1.use_count() << endl;
    cout << ptrB2.use_count() << endl;
    cout << ptrB3.use_count() << endl;
}

这个打印出的结果是3

# include <iostream>
# include <tr1/memory>
using namespace std;
class A {
public:
    A() {
        cout << "construct A!!!" << endl;
    }
    ;
    ~A() {
        cout << "destruct A!!!" << endl;
    }
    ;
};
class B: public A {
public:
    B() {
        cout << "construct B!!!" << endl;
    }
    ;
    ~B() {
        cout << "destruct B!!!" << endl;
    }
    ;
};
int main() {
//  B* ptrB0 = new B();
    B * pint = new B();
    std::tr1::shared_ptr<B> ptrB1(pint);

    std::tr1::shared_ptr<B> ptrB2(pint);

    std::tr1::shared_ptr<B> ptrB3(pint);
    cout << ptrB1.use_count() << endl;
    cout << ptrB2.use_count() << endl;
    cout << ptrB3.use_count() << endl;
}

这个的打印结果是1.

这也就说明引用计数和所对应的对象不是强相关的,自我理解应该是这样,当shared_ptr初始化时(通过非同类对象),会申请一块内存进行引用计数。当通过同类对象进行初始化时,就会使用同一块内存,而不是新申请。也就是只有通过赋值操作符和复制构造函数才会使引用计数有效。

定义类时应该考虑的问题:

如果只是想简单的添加机能,首先要考虑是否只定义一个nonmember函数或templates就可以了。
定义一整个class家族的话,就应该定义class template。

对象切割问题:

子类对象以by value传递,并被视为一个基类对象,会导致基类的复制构造函数被调用,而造成此对象的行为像个derived对象的特化性质全部被切掉,仅留下base class 对象

pass by value和pass by const reference

内置类型和stl的迭代器和函数对象都适合传value。这样效率更高。之所以用reference,主要是因为reference高效且能避免切割问题。

non local static 对象的交互问题

当不同文件的nonlocal static对象之间存在依赖关系时,我们可以吧其转换为local static对象,通过在函数中定义static对象,并返回其引用,这样就可以保证在别的对象获取到这个对象时,其已经时初始化过的。
但不论是local还是non local,在多线程下等待某事发生都会有麻烦,处理这种麻烦的一种做法是,在单线程启动阶段手工调用所有reference return函数,这样就可以消除与初始化有关的”竞速形式“。

对封装而言只有private(封装)和其他(不封装)两种访问权限。protected并不比public更具有封装性。


<think>好的,我现在需要回答用户关于C++shared_ptr的使用方法及示例的问题。首先,我应该回忆一下自己对shared_ptr的了解,并结合提供的引用资料来确保信息的准确性。 首先,shared_ptrC++智能指针的一种,用于自动管理动态分配的内存,防止内存泄漏。根据引用1,shared_ptr采用引用计数机制,多个shared_ptr可以共享同一个对象,当最后一个shared_ptr被销毁时,对象才会被释放。引用2提到shared_ptr允许拷贝和赋值,这说明它们支持共享所有权。引用3则补充了shared_ptr销毁对象的条件,即最后一个拥有对象shared_ptr被销毁或重置时。 接下来,我需要组织回答的结构。用户需要使用方法及示例,所以应该分步骤讲解,并附上代码示例。可能的要点包括:创建shared_ptr、共享所有权、自定义删除器、注意事项等。 创建shared_ptr部分,需要说明如何用make_shared或直接构造。共享所有权要展示多个指针指向同一对象的情况。自定义删除器可以举文件关闭的例子。注意事项要提到循环引用的问题,并引入weak_ptr作为解决方案。 在写代码示例时,要确保语法正确,并且每段代码有注释解释。同时,要引用提供的资料中的信息,比如在说明引用计数时引用[^1],在提到拷贝赋值时引用[^2],在销毁条件时引用[^3]。 另外,用户的问题可能涉及相关技术问题,比如循环引用,所以需要解释问题原因和解决方法。这里需要提到weak_ptr,并给出示例,如引用3中提到的weak_ptr用于打破循环。 最后,生成相关问题时要围绕shared_ptr的扩展内容,比如与unique_ptr的区别、循环引用的解决、性能分析等。 需要检查是否符合用户的要求:使用中文、正确格式的数学公式(如果有的话)、引用标识、代码块格式正确。但在这个问题中可能没有数学公式,但如果有涉及计算的地方,需要按格式处理。 总结回答的结构: 1. shared_ptr基本概念和引用计数机制 2. 创建方法(make_shared) 3. 共享所有权示例 4. 自定义删除器 5. 注意事项(循环引用和weak_ptr) 6. 示例代码 7. 相关问题 确保每个部分都简洁明了,引用正确的资料,示例代码正确无误。</think>### C++ shared_ptr 使用方法及示例 #### 1. shared_ptr 基本概念 `shared_ptr` 是 C++ 标准库提供的智能指针,通过**引用计数机制**管理动态分配的内存。多个 `shared_ptr` 可以指向同一对象,当最后一个 `shared_ptr` 被销毁或重置时,对象内存会被自动释放[^1]。 --- #### 2. 使用方法 ##### (1) 创建 shared_ptr 推荐使用 `std::make_shared` 创建,既安全又高效: ```cpp #include <memory> // 创建指向 int 的 shared_ptr auto sp1 = std::make_shared<int>(42); // 直接构造(不推荐,可能引发异常安全问题) std::shared_ptr<int> sp2(new int(100)); ``` ##### (2) 共享所有权 多个 `shared_ptr` 可以共享同一对象,引用计数自动增加/减少: ```cpp auto sp3 = sp1; // 引用计数变为2 sp3.reset(); // 引用计数恢复为1 ``` ##### (3) 自定义删除器 默认删除器调用 `delete`,但可自定义(如用于文件句柄): ```cpp std::shared_ptr<FILE> file_ptr( fopen("test.txt", "r"), [](FILE* f) { if (f) fclose(f); } ); ``` --- #### 3. 注意事项 ##### 循环引用问题 若两个 `shared_ptr` 互相引用,会导致内存泄漏。需用 `weak_ptr` 打破循环: ```cpp class B; // 前向声明 class A { public: std::shared_ptr<B> b_ptr; }; class B { public: std::weak_ptr<A> a_weak_ptr; // 使用 weak_ptr 避免循环引用 }; ``` --- #### 4. 完整示例 ```cpp #include <iostream> #include <memory> struct Data { Data() { std::cout << "Data created\n"; } ~Data() { std::cout << "Data destroyed\n"; } }; int main() { // 创建 shared_ptr auto d1 = std::make_shared<Data>(); { auto d2 = d1; // 引用计数变为2 std::cout << "d2 使用中,引用计数=" << d2.use_count() << "\n"; } // d2 销毁,引用计数恢复为1 std::cout << "d1 引用计数=" << d1.use_count() << "\n"; return 0; } ``` **输出**: ``` Data created d2 使用中,引用计数=2 d1 引用计数=1 Data destroyed ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值