C++ C11 智能指针基本操作

std::weak_ptr 的主要作用 是提供一种不影响引用计数的弱引用,解决循环引用问题,并提供安全的弱引用访问方式。

控制块中的引用计数和弱引用计数 分别用于管理 std::shared_ptr 和 std::weak_ptr 的生命周期。

std::shared_ptr 的引用计数降为零 时,管理的对象会被销毁,但控制块会继续存在,直到所有 std::weak_ptr 也被销毁。

#include <iostream>
#include <memory>

int main() {
    std::shared_ptr<int> sharedPtr = std::make_shared<int>(42);
    std::weak_ptr<int> weakPtr = sharedPtr;

    sharedPtr.reset(); // 销毁资源

    if (auto sp = weakPtr.lock()) {
        std::cout << "Resource is available: " << *sp << std::endl;
    } else {
        std::cout << "Resource has been destroyed, but control block still exists" << std::endl;
    }

    return 0;
}

1. 创建 std::shared_ptr

可以使用 std::make_shared 或直接构造 std::shared_ptr 来创建智能指针。

#include <iostream>
#include <memory>

int main() {
    // 使用 std::make_shared 创建
    std::shared_ptr<int> ptr1 = std::make_shared<int>(10);

    // 直接构造 std::shared_ptr
    std::shared_ptr<int> ptr2(new int(20));

    // 检查指针是否为空
    if (ptr1) {
        std::cout << "ptr1 points to " << *ptr1 << std::endl;
    }

    if (ptr2) {
        std::cout << "ptr2 points to " << *ptr2 << std::endl;
    }

    return 0;
}

1.2 注意事项

1.2.1 不要用一个原始指针初始化多个shared_ptr

int* ptr =new int();
std::shared_prt<int> p1(ptr);
std::shared_prt<int> p2(ptr);

1.2.2 2 不要在函数实参中创建shared_ptr

functionA(shared_ptr<int>(new int), g()); 
因为 C++ 的函数参数的计算顺序在不同的编译器不同的约定下可能是不一样的 ,一般是从右到左,但也
可能从左到右,所以,可能的过程是先 new int ,然后调用 g() ,如果恰好 g() 发生异常,而 shared_ptr
没有创建, 则 int 内存泄漏了,正确的写法应该是先创建智能指针

2. 访问和操作对象

过 operator* 和 operator-> 可以访问 std::shared_ptr 所管理的对象 

#include <iostream>
#include <memory>

class MyClass {
public:
    void display() const {
        std::cout << "MyClass::display()" << std::endl;
    }
};

int main() {
    std::shared_ptr<MyClass> ptr = std::make_shared<MyClass>();

    // 使用 operator->
    ptr->display();

    // 使用 operator*
    (*ptr).display();

    return 0;
}

3. 复制和赋值

std::shared_ptr 可以被复制和赋值,复制后的指针将共享同一个对象。

#include <iostream>
#include <memory>

int main() {
    std::shared_ptr<int> ptr1 = std::make_shared<int>(30);
    std::shared_ptr<int> ptr2 = ptr1; // 复制指针

    std::cout << "ptr1 points to " << *ptr1 << std::endl;
    std::cout << "ptr2 points to " << *ptr2 << std::endl;

    // 检查引用计数
    std::cout << "Reference count: " << ptr1.use_count() << std::endl;

    return 0;
}

4. 重置 std::shared_ptr

可以使用 reset() 方法重置 std::shared_ptr,从而释放其管理的对象.

std::shared_ptr 提供了 reset() 方法,用于重置智能指针的管理对象。重置的行为取决于 reset() 是否带参数,以及该智能指针是否被多个智能指针共享

#include <iostream>
#include <memory>

int main() {
    std::shared_ptr<int> ptr = std::make_shared<int>(40);

    std::cout << "ptr points to " << *ptr << std::endl;

    // 重置指针
    ptr.reset();

    // 检查指针是否为空
    if (!ptr) {
        std::cout << "ptr is now empty" << std::endl;
    }

    return 0;
}

4.1reset() 不带参数

当 reset() 不带参数时,智能指针将释放其当前管理的对象(如果是唯一持有者),并将其设置为空指针

#include <iostream>
#include <memory>

int main() {
    std::shared_ptr<int> ptr = std::make_shared<int>(42);

    std::cout << "Before reset: " << (ptr ? "not empty" : "empty") << std::endl;

    ptr.reset(); // 不带参数的 reset,将 ptr 设为空指针

    std::cout << "After reset: " << (ptr ? "not empty" : "empty") << std::endl;

    return 0;
}

4.2 reset() 带参数

当 reset() 带参数时,智能指针将释放其当前管理的对象(如果是唯一持有者),并开始管理新的对象。

#include <iostream>
#include <memory>

int main() {
    std::shared_ptr<int> ptr = std::make_shared<int>(42);

    std::cout << "Before reset: " << *ptr << std::endl;

    ptr.reset(new int(100)); // 带参数的 reset,ptr 现在管理新的对象

    std::cout << "After reset: " << *ptr << std::endl;

    return 0;
}

4.3 被多个智能指针管理的情形

当一个对象被多个 std::shared_ptr 管理时,只有当最后一个 std::shared_ptr 被销毁或重置时,才会释放对象。

#include <iostream>
#include <memory>

void print_ref_count(const std::shared_ptr<int>& ptr) {
    std::cout << "Reference count: " << ptr.use_count() << std::endl;
}

int main() {
    std::shared_ptr<int> ptr1 = std::make_shared<int>(42);
    print_ref_count(ptr1); // 引用计数为 1

    {
        std::shared_ptr<int> ptr2 = ptr1; // 复制 ptr1,引用计数加 1
        print_ref_count(ptr1); // 引用计数为 2

        ptr1.reset(); // ptr1 被重置,但对象仍被 ptr2 管理
        print_ref_count(ptr2); // 引用计数为 1
    }

    // 作用域结束,ptr2 被销毁,对象引用计数为 0,被释放
    std::cout << "ptr1 is " << (ptr1 ? "not empty" : "empty") << std::endl;

    return 0;
}

4.4 带参数的 reset() 在共享对象时的行为

#include <iostream>
#include <memory>

void print_ref_count(const std::shared_ptr<int>& ptr) {
    std::cout << "Reference count: " << ptr.use_count() << std::endl;
}

int main() {
    std::shared_ptr<int> ptr1 = std::make_shared<int>(42);
    print_ref_count(ptr1); // 引用计数为 1

    {
        std::shared_ptr<int> ptr2 = ptr1; // 复制 ptr1,引用计数加 1
        print_ref_count(ptr1); // 引用计数为 2

        ptr1.reset(new int(100)); // ptr1 现在管理一个新的对象
        print_ref_count(ptr2); // 原对象引用计数为 1
        print_ref_count(ptr1); // 新对象引用计数为 1
    }

    // 作用域结束,ptr2 被销毁,原对象引用计数为 0,被释放
    std::cout << "ptr1 points to " << *ptr1 << std::endl;

    return 0;
}

在上述示例中:

1. ptr1 和 ptr2 共享同一个对象。

2. 当 ptr1 使用带参数的 reset(new int(100)) 时,ptr1 开始管理一个新的对象,而原对象仍然由 ptr2 管理。

3. 当 ptr2 超出作用域被销毁时,原对象的引用计数降为 0,被释放。

4. ptr1 现在管理一个新的对象,其引用计数为 1。

5. 获取原始指针

可以使用 get() 方法获取 std::shared_ptr 所管理的原始指针,但要小心使用,避免管理问题。

#include <iostream>
#include <memory>

int main() {
    std::shared_ptr<int> ptr = std::make_shared<int>(50);

    // 获取原始指针
    int* raw_ptr = ptr.get();

    std::cout << "Raw pointer points to " << *raw_ptr << std::endl;

    return 0;
}

6. 使用自定义删除器

#include <iostream>
#include <memory>

void custom_deleter(int* ptr) {
    std::cout << "Custom deleter called" << std::endl;
    delete ptr;
}

int main() {
    std::shared_ptr<int> ptr(new int(60), custom_deleter);

    std::cout << "ptr points to " << *ptr << std::endl;

    return 0;
}

7. 循环引用问题

7.1 问题的产生

a 和 b 互相引用,导致它们的引用计数都不为 0,即使在 main 函数结束时,a 和 b 也不会被销毁,从而导致内存泄漏

#include <iostream>
#include <memory>

class B; // 前向声明

class A {
public:
    std::shared_ptr<B> ptrB;
    ~A() { std::cout << "A destroyed" << std::endl; }
};

class B {
public:
    std::shared_ptr<A> ptrA;
    ~B() { std::cout << "B destroyed" << std::endl; }
};

int main() {
    std::shared_ptr<A> a = std::make_shared<A>();
    std::shared_ptr<B> b = std::make_shared<B>();

    a->ptrB = b;
    b->ptrA = a;

    // 此时 a 和 b 的引用计数都不为 0,导致内存泄漏
    return 0;
}

7.2 解决方案:使用 std::weak_ptr

可以使用 std::weak_ptr。std::weak_ptr 是一种不增加引用计数的智能指针。它可以从 std::shared_ptr 创建,但不会影响对象的生命周期。通过 std::weak_ptr,可以打破循环引用。

#include <iostream>
#include <memory>

class B; // 前向声明

class A {
public:
    std::shared_ptr<B> ptrB;
    ~A() { std::cout << "A destroyed" << std::endl; }
};

class B {
public:
    std::weak_ptr<A> ptrA; // 使用 std::weak_ptr 解决循环引用
    ~B() { std::cout << "B destroyed" << std::endl; }
};

int main() {
    std::shared_ptr<A> a = std::make_shared<A>();
    std::shared_ptr<B> b = std::make_shared<B>();

    a->ptrB = b;
    b->ptrA = a; // 使用 weak_ptr 避免循环引用

    return 0;
}

7.3 如何使用 std::weak_ptr

7.3.1 创建 std::weak_ptr

std::weak_ptr 可以从 std::shared_ptr 创建

std::shared_ptr<A> a = std::make_shared<A>();
std::weak_ptr<A> weak_a = a;

7.3.2 锁定 std::weak_ptr

std::weak_ptr 本身不能直接访问对象,需要通过 lock() 方法将其转换为 std::shared_ptr。如果对象已被销毁,lock() 返回一个空的 std::shared_ptr

if (auto shared_a = weak_a.lock()) {
    // 可以安全地使用 shared_a
    shared_a->some_method();
} else {
    // 对象已被销毁
}

7.3.3 实例

#include <iostream>
#include <memory>

class B; // 前向声明

class A {
public:
    std::shared_ptr<B> ptrB;
    ~A() { std::cout << "A destroyed" << std::endl; }
};

class B {
public:
    std::weak_ptr<A> ptrA; // 使用 std::weak_ptr 解决循环引用
    ~B() { std::cout << "B destroyed" << std::endl; }

    void useA() {
        if (auto a = ptrA.lock()) {
            std::cout << "Using A" << std::endl;
        } else {
            std::cout << "A is no longer available" << std::endl;
        }
    }
};

int main() {
    std::shared_ptr<A> a = std::make_shared<A>();
    std::shared_ptr<B> b = std::make_shared<B>();

    a->ptrB = b;
    b->ptrA = a; // 使用 weak_ptr 避免循环引用

    b->useA(); // 使用 A

    return 0;
}

7.3.4 expired()

使用 wp 前需要调用 wp.expired() 函数判断一下
weak_ptr<int> wp;
shared_ptr<int> sp_ok;
{
shared_ptr<int> sp(new int(1)); //sp.use_count()==1
wp = sp; //wp不会改变引用计数,所以sp.use_count()==1
sp_ok = wp.lock(); //wp没有重载->操作符。只能这样取所指向的对象
}
if(wp.expired()) {
cout << "shared_ptr is destroy" << endl;
} else {
cout << "shared_ptr no destroy" << endl;
}

8.别名构造函数

8.1 基本概念

std::shared_ptr 的别名构造函数允许你创建一个新 std::shared_ptr,它共享另一个 std::shared_ptr 的所有权(即共享引用计数),但指向不同的对象。这可以用于以下几种情况:

管理一个对象的子对象或成员。

在容器或复合对象中共享控制块,但指向不同的元素。

8.2 别名构造函数的使用

template<class Y, class T>
shared_ptr(const shared_ptr<Y>& r, T* p) noexcept;

r 是一个已有的 std::shared_ptr,它管理某个对象。

p 是一个原始指针,指向要管理的不同对象。

新创建的 std::shared_ptr 将共享 r 的控制块(包括引用计数),但指向 p 所指向的对象。

8.3 示例

8.3.1 管理对象的成员

#include <iostream>
#include <memory>

struct Base {
    int value;
    Base(int v) : value(v) {}
};

struct Derived : public Base {
    Derived(int v) : Base(v) {}
};

int main() {
    // 创建一个 std::shared_ptr 管理 Derived 对象
    std::shared_ptr<Derived> derivedPtr = std::make_shared<Derived>(100);

    // 使用别名构造函数创建一个 std::shared_ptr 管理 Base 子对象
    std::shared_ptr<Base> basePtr(derivedPtr, derivedPtr.get());

    std::cout << "Derived value: " << derivedPtr->value << std::endl;
    std::cout << "Base value: " << basePtr->value << std::endl;

    std::cout << "Derived reference count: " << derivedPtr.use_count() << std::endl;
    std::cout << "Base reference count: " << basePtr.use_count() << std::endl;

    return 0;
}

在这个示例中:

1. derivedPtr 是一个管理 Derived 对象的 std::shared_ptr。

2. basePtr 使用别名构造函数创建,管理 Derived 对象的 Base 部分。

3. derivedPtr 和 basePtr 共享同一个控制块,引用计数为 2。

8.3.2 管理对象的成员

#include <iostream>
#include <memory>

struct MyStruct {
    int x;
    int y;
    MyStruct(int a, int b) : x(a), y(b) {}
};

int main() {
    // 创建一个 std::shared_ptr 管理 MyStruct 对象
    std::shared_ptr<MyStruct> structPtr = std::make_shared<MyStruct>(10, 20);

    // 使用别名构造函数创建 std::shared_ptr 仅管理 x 成员
    std::shared_ptr<int> xPtr(structPtr, &structPtr->x);

    // 使用别名构造函数创建 std::shared_ptr 仅管理 y 成员
    std::shared_ptr<int> yPtr(structPtr, &structPtr->y);

    std::cout << "structPtr x: " << structPtr->x << ", y: " << structPtr->y << std::endl;
    std::cout << "xPtr: " << *xPtr << std::endl;
    std::cout << "yPtr: " << *yPtr << std::endl;

    std::cout << "structPtr reference count: " << structPtr.use_count() << std::endl;
    std::cout << "xPtr reference count: " << xPtr.use_count() << std::endl;
    std::cout << "yPtr reference count: " << yPtr.use_count() << std::endl;

    return 0;
}

1. structPtr 是一个管理 MyStruct 对象的 std::shared_ptr。

2. xPtr 使用别名构造函数创建,管理 MyStruct 对象的 x 成员。

3. yPtr 使用别名构造函数创建,管理 MyStruct 对象的 y 成员。

4. structPtr、xPtr 和 yPtr 共享同一个控制块,引用计数为 3。

9.shared_from_this

9.1 作用

shared_from_this 的主要作用是允许对象安全地创建一个 std::shared_ptr 指向自身,特别是在对象的成员函数中。它解决了直接使用 this 指针可能导致的问题,例如对象提前销毁或多重所有权的问题。

9.2 使用方法

要使用 shared_from_this,类需要继承 std::enable_shared_from_this<T>,其中 T 是类的类型。这样,类的实例就可以调用 shared_from_this() 方法来生成一个 std::shared_ptr 指向自身。

9.3 示例

#include <iostream>
#include <memory>
using namespace std;



class MyClass : public std::enable_shared_from_this<MyClass> {
public:
    void show() {
        std::shared_ptr<MyClass> self = shared_from_this();
        std::cout << "MyClass instance at: " << this << ", use count: " << self.use_count() << std::endl;
    }

    ~MyClass() {
        std::cout << "MyClass destructor called for: " << this << std::endl;
    }
};

int main() {
    std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();
    ptr1->show();

	std::cout << "Use count after show() called: " << ptr1.use_count() << std::endl;
	
    std::shared_ptr<MyClass> ptr2 = ptr1->shared_from_this();
    std::cout << "Use count after shared_from_this: " << ptr1.use_count() << std::endl;

    return 0;
}

ptr2 是通过 shared_from_this() 获取的另一个指向同一对象的 std::shared_ptr,引用计数正确更新。

MyClass instance at: 0x1bd0e80, use count: 2

--> 调用 ptr1->show() 时,shared_from_this() 返回一个新的 std::shared_ptr,增加了引用计数。
Use count after show() called: 1

--> show()函数体结束。
Use count after shared_from_this: 2
MyClass destructor called for: 0x1bd0e80

9.4 注意事项

9.4.1. 必须通过 std::shared_ptr 创建对象

对象必须通过 std::shared_ptr 创建,才能正确使用 shared_from_this()。否则,会导致未定义行为。错误示例:MyClass obj;

std::shared_ptr<MyClass> sp = obj.shared_from_this(); // 未定义行为正确示例:std::shared_ptr<MyClass> sp = std::make_shared<MyClass>();

std::shared_ptr<MyClass> sp2 = sp->shared_from_this(); // 正确

9.4.2. 避免在构造函数和析构函数中使用 shared_from_this

在对象的构造函数和析构函数中调用 shared_from_this() 是未定义行为,因为在这些时候,std::shared_ptr 可能还没有完全构建或已经销毁。错误示例:class MyClass : public std::enable_shared_from_this<MyClass> {

public:

    MyClass() {

        std::shared_ptr<MyClass> self = shared_from_this(); // 未定义行为

    }

};

9.4.3. 正确使用 shared_from_this

在需要从成员函数中获取 std::shared_ptr 指向当前对象时,使用 shared_from_this 是一个好的选择,可以确保对象的生命周期管理。正确示例:void MyClass::someMemberFunction() {

    std::shared_ptr<MyClass> self = shared_from_this();

    // 安全地使用 self

}

10.std::shared_ptr 在某些方面是线程安全的,但在其他方面则不是

10.1 引用计数是线程安全的

std::shared_ptr 的引用计数操作是线程安全的。这意味着多个线程可以同时持有并操作同一个 std::shared_ptr 实例,而不需要额外的同步机制。例如,当一个 std::shared_ptr 被复制或销毁时,引用计数会自动更新,并且这个更新操作是线程安全的。

#include <iostream>
#include <memory>
#include <thread>

void threadFunc(std::shared_ptr<int> ptr) {
    std::cout << "In thread: " << *ptr << std::endl;
}

int main() {
    std::shared_ptr<int> ptr = std::make_shared<int>(42);

    std::thread t1(threadFunc, ptr);
    std::thread t2(threadFunc, ptr);

    t1.join();
    t2.join();

    return 0;
}

在这个示例中,两个线程同时持有并使用同一个 std::shared_ptr,不会产生竞争条件,因为引用计数操作是线程安全的。

10.2 对象操作和指针本身的读写不是线程安全的

虽然 std::shared_ptr 的引用计数是线程安全的,但它所管理的对象的操作和指针本身的读写并不是线程安全的。如果多个线程需要同时读写对象或修改指针,必须使用额外的同步机制(如互斥锁 std::mutex)来保护这些操作。

#include <iostream>
#include <memory>
#include <thread>
#include <mutex>

std::mutex mtx;

void threadFunc(std::shared_ptr<int> ptr) {
    std::lock_guard<std::mutex> lock(mtx);
    *ptr += 1;
    std::cout << "In thread: " << *ptr << std::endl;
}

int main() {
    std::shared_ptr<int> ptr = std::make_shared<int>(42);

    std::thread t1(threadFunc, ptr);
    std::thread t2(threadFunc, ptr);

    t1.join();
    t2.join();

    std::cout << "Final value: " << *ptr << std::endl;

    return 0;
}

在这个示例中,std::mutex 被用来保护对共享对象的修改操作,以避免数据竞争。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值