说明:本文主要解释auto_ptr、unique_ptr、shared_ptr、weak_ptr这几种智能指针。接下来我们对每一个指针类型进行详细说明并给出一些基本的使用方式,重在深入理解。

在 C++ 中,auto_ptr、unique_ptr、shared_ptr 和 weak_ptr四种智能指针主要用于管理动态分配的对象的生命周期。它们在所有权管理和内存安全方面有所不同。

1 auto_ptr(已废弃,需了解)

auto_ptr 是 C++98 中引入的智能指针,但在 C++11 中被标记为已废弃。它实现了独占式所有权,即一个 auto_ptr 对象拥有对动态分配对象的唯一所有权。当 auto_ptr 被销毁时,它会自动释放所拥有的对象。然而,auto_ptr 存在一些严重的问题,如无法正确处理数组和容器等情况,因此不推荐使用。但是关于auto_ptr的使用,我们还是需要了解。

以下是一个较为完整的示例,演示了 auto_ptr 的基本操作:

#include <iostream>
#include <memory>

class MyClass {
public:
    MyClass(int data) : data(data) {
        std::cout << "MyClass object created with data: " << data << std::endl;
    }

    ~MyClass() {
        std::cout << "MyClass object destroyed with data: " << data << std::endl;
    }

    void printData() {
        std::cout << "Data: " << data << std::endl;
    }

private:
    int data;
};

int main() {
    // 创建 auto_ptr,使用 new 运算符
    std::auto_ptr<MyClass> myClassPtr(new MyClass(42));

    // 使用箭头运算符访问对象成员函数
    myClassPtr->printData();

    // 使用 * 运算符解引用 auto_ptr,并访问对象成员函数
    (*myClassPtr).printData();

    // 转移所有权到另一个 auto_ptr
    std::auto_ptr<MyClass> myClassPtr2 = myClassPtr;

    // 使用箭头运算符访问对象成员函数
    myClassPtr2->printData();

    // 判断 auto_ptr 是否为空
    if (myClassPtr) {
        std::cout << "myClassPtr is not empty." << std::endl;
    } else {
        std::cout << "myClassPtr is empty." << std::endl;
    }

    // 重置 auto_ptr,解除与资源的关联
    myClassPtr.reset();

    // 判断 auto_ptr 是否为空
    if (myClassPtr2) {
        std::cout << "myClassPtr2 is not empty." << std::endl;
    } else {
        std::cout << "myClassPtr2 is empty." << std::endl;
    }

    return 0;
}

这里需要注意:

  • auto_ptr 在所有权转移后会导致原来的指针为空,因此在使用它时需要格外小心。
  • auto_ptr 在容器等场景中的行为也可能不符合预期。因此,建议使用更安全、功能更强大的 unique_ptr 或其他智能指针替代 auto_ptr。

2 unique_ptr

unique_ptr 是 C++11 引入的智能指针,取代了 auto_ptr。它也实现了独占式所有权,但是提供了更严格的所有权管理和更好的性能。unique_ptr 不能被复制,只能通过移动语义来转移所有权。它使用了 RAII(资源获取即初始化)的原则,确保在作用域结束时自动释放所拥有的对象。unique_ptr 还提供了自定义删除器的能力,以便释放非默认方式分配的资源。

下面是一个使用 unique_ptr 的较为完整的示例,涵盖了常见的基本操作:

#include <iostream>
#include <memory>

class MyClass {
public:
    MyClass(int data) : data(data) {
        std::cout << "MyClass object created with data: " << data << std::endl;
    }

    ~MyClass() {
        std::cout << "MyClass object destroyed with data: " << data << std::endl;
    }

    void printData() {
        std::cout << "Data: " << data << std::endl;
    }

private:
    int data;
};

int main() {
    // 创建 unique_ptr,使用 new 运算符
    std::unique_ptr<MyClass> myClassPtr(new MyClass(42));

    // 使用箭头运算符访问对象成员函数
    myClassPtr->printData();

    // 使用 * 运算符解引用 unique_ptr,并访问对象成员函数
    (*myClassPtr).printData();

    // 判断 unique_ptr 是否为空
    if (myClassPtr) {
        std::cout << "myClassPtr is not empty." << std::endl;
    } else {
        std::cout << "myClassPtr is empty." << std::endl;
    }

    // 重置 unique_ptr,解除与资源的关联
    myClassPtr.reset();

    // 判断 unique_ptr 是否为空
    if (myClassPtr) {
        std::cout << "myClassPtr is not empty." << std::endl;
    } else {
        std::cout << "myClassPtr is empty." << std::endl;
    }

    return 0;
}

这个示例演示了 unique_ptr 的常见用法,包括创建和初始化、访问对象成员函数、重置和判断是否为空。通过使用 unique_ptr,我们可以自动管理动态分配的对象,并确保只有一个 unique_ptr 拥有对资源的所有权,避免资源的共享和所有权冲突的问题。

3 shared_ptr

shared_ptr 是一种共享所有权的智能指针。它可以跨多个 shared_ptr 对象共享对同一动态分配对象的所有权。shared_ptr 内部使用引用计数来跟踪对象的引用数,并在引用数为零时自动释放所拥有的对象。shared_ptr 具有拷贝构造函数和拷贝赋值运算符,允许多个 shared_ptr 共享同一对象。当最后一个 shared_ptr 被销毁时,它会自动释放对象。然而,shared_ptr 有可能产生循环引用,导致内存泄漏。为了解决这个问题,C++11 引入了 weak_ptr。

下面是一个使用 shared_ptr 的较为完整的示例,涵盖了常见的基本操作:

#include <iostream>
#include <memory>

class MyClass {
public:
    MyClass(int data) : data(data) {
        std::cout << "MyClass object created with data: " << data << std::endl;
    }

    ~MyClass() {
        std::cout << "MyClass object destroyed with data: " << data << std::endl;
    }

    void printData() {
        std::cout << "Data: " << data << std::endl;
    }

private:
    int data;
};

int main() {
    // 创建 shared_ptr,使用 make_shared 函数
    std::shared_ptr<MyClass> myClassPtr = std::make_shared<MyClass>(42);

    // 使用箭头运算符访问对象成员函数
    myClassPtr->printData();

    // 使用 * 运算符解引用 shared_ptr,并访问对象成员函数
    (*myClassPtr).printData();

    // 获取 shared_ptr 的引用计数
    std::cout << "Reference count: " << myClassPtr.use_count() << std::endl;

    // 创建新的 shared_ptr,与已有的 shared_ptr 共享资源
    std::shared_ptr<MyClass> myClassPtr2 = myClassPtr;

    // 获取共享资源的引用计数
    std::cout << "Reference count: " << myClassPtr.use_count() << std::endl;

    // 重置 shared_ptr,解除与资源的关联
    myClassPtr.reset();

    // 获取共享资源的引用计数
    std::cout << "Reference count: " << myClassPtr2.use_count() << std::endl;

    // 判断 shared_ptr 是否为空
    if (myClassPtr) {
        std::cout << "myClassPtr is not empty." << std::endl;
    } else {
        std::cout << "myClassPtr is empty." << std::endl;
    }

    return 0;
}

这个示例演示了 shared_ptr 的常见用法,包括创建和初始化、访问对象成员函数、引用计数操作、重置和判断是否为空。通过使用 shared_ptr,我们可以自动管理动态分配的对象,避免手动释放资源和内存泄漏的问题。

4 weak_ptr

weak_ptr 是 C++ 中用于解决循环引用和避免悬空指针的智能指针类型。它通常与 shared_ptr 一起使用,用于监测对象的生命周期而不增加引用计数。 weak_ptr 的常用用法如下:

  • 解决循环引用:当两个或多个对象互相持有对方的 shared_ptr,形成循环引用时,可以使用 weak_ptr 来打破循环引用并释放资源。
  • 检测对象是否存在:通过调用 expired() 函数,可以检查 weak_ptr 是否指向有效的对象。如果对象已被销毁,expired() 返回 true,否则返回 false。
  • 获取 shared_ptr:可以通过调用 lock() 函数获取指向 weak_ptr 所管理对象的 shared_ptr。如果对象存在,则返回有效的 shared_ptr;如果对象已被销毁,返回空的 shared_ptr。

4.1 weak_ptr 的常用用法

weak_ptr 的常用用法代码实现如下:

#include <iostream>
#include <memory>

class MyClass {
public:
    ~MyClass() {
        std::cout << "MyClass object destroyed." << std::endl;
    }
};

int main() {
    std::weak_ptr<MyClass> weakPtr;

    {
        std::shared_ptr<MyClass> sharedPtr = std::make_shared<MyClass>();
        weakPtr = sharedPtr;

        if (weakPtr.expired()) {
            std::cout << "weakPtr is expired." << std::endl;
        } else {
            std::cout << "weakPtr is not expired." << std::endl;

            std::shared_ptr<MyClass> lockedPtr = weakPtr.lock();
            if (lockedPtr) {
                std::cout << "Weak pointer locked successfully." << std::endl;
                // 在这里可以使用 lockedPtr 操作对象
            } else {
                std::cout << "Weak pointer failed to lock." << std::endl;
            }
        }
    }

    if (weakPtr.expired()) {
        std::cout << "weakPtr is expired." << std::endl;
    } else {
        std::cout << "weakPtr is not expired." << std::endl;

        std::shared_ptr<MyClass> lockedPtr = weakPtr.lock();
        if (lockedPtr) {
            std::cout << "Weak pointer locked successfully." << std::endl;
            // 在这里可以使用 lockedPtr 操作对象
        } else {
            std::cout << "Weak pointer failed to lock." << std::endl;
        }
    }

    return 0;
}

在这个示例中,我们使用了 expired() 和 lock() 函数来检测和操作 weak_ptr。

通过这个示例,我们展示了如何结合使用 expired() 函数和 lock() 函数来检测 weak_ptr 是否过期,并使用安全的 shared_ptr 进行对象操作。

4.2 循环引用问题

我们从前面的shared_ptr中知道,当使用 shared_ptr 时,每个 shared_ptr 对象内部都有一个引用计数(reference count),用于跟踪对象的引用数。引用计数表示有多少个 shared_ptr 共享对同一对象的所有权。当一个 shared_ptr 对象被复制或拷贝构造时,其引用计数会增加。当 shared_ptr 对象被销毁(超出作用域、被重新赋值或显式调用 reset())时,其引用计数会减少。当引用计数为零时,表示没有 shared_ptr 对象拥有对该对象的所有权,此时会释放对象的内存。

那么什么是循环引用计数呢?

循环引用指的是两个或多个对象彼此持有对方的 shared_ptr,导致它们的引用计数永远无法减至零,从而造成内存泄漏。这是因为即使没有其他指针引用这些对象,它们之间的循环引用仍使引用计数保持非零值,对象无法被销毁。,一个具体的代码案例如下所示:

#include <iostream>
#include <memory>

class MyClassB;  // 前向声明

class MyClassA {
public:
    std::shared_ptr<MyClassB> bPtr;

    MyClassA() {
        std::cout << "MyClassA constructed." << std::endl;
    }

    ~MyClassA() {
        std::cout << "MyClassA destroyed." << std::endl;
    }
};

class MyClassB {
public:
    std::shared_ptr<MyClassA> aPtr;

    MyClassB() {
        std::cout << "MyClassB constructed." << std::endl;
    }

    ~MyClassB() {
        std::cout << "MyClassB destroyed." << std::endl;
    }
};

int main() {
    std::shared_ptr<MyClassA> aPtr = std::make_shared<MyClassA>();
    std::shared_ptr<MyClassB> bPtr = std::make_shared<MyClassB>();

    // 形成循环引用
    aPtr->bPtr = bPtr;
    bPtr->aPtr = aPtr;

    return 0;
}

为了解决这个问题,C++11 引入了 weak_ptr。解决引用计数问题可以这样做:

#include <iostream>
#include <memory>

class MyClassB;

class MyClassA {
public:
    std::shared_ptr<MyClassB> bPtr;

    MyClassA() {
        std::cout << "MyClassA constructed." << std::endl;
    }

    ~MyClassA() {
        std::cout << "MyClassA destroyed." << std::endl;
    }
};

class MyClassB {
public:
    std::weak_ptr<MyClassA> aPtr;

    MyClassB() {
        std::cout << "MyClassB constructed." << std::endl;
    }

    ~MyClassB() {
        std::cout << "MyClassB destroyed." << std::endl;
    }
};

int main() {
    std::shared_ptr<MyClassA> aPtr = std::make_shared<MyClassA>();
    std::shared_ptr<MyClassB> bPtr = std::make_shared<MyClassB>();

    aPtr->bPtr = bPtr;
    bPtr->aPtr = aPtr;

    return 0;
}

在这个修改后的示例中,MyClassB 的成员变量 aPtr 由 shared_ptr 改为 weak_ptr。这样,MyClassB 对象持有的是MyClassA 对象的弱引用,不会增加引用计数,也不会阻止 MyClassA 对象的销毁。当程序结束时,引用计数会降至零,两个对象的析构函数会被正确调用,内存得到正确释放,避免了内存泄漏。也就是使用 weak_ptr 可以打破循环引用,避免内存泄漏的发生。

但是如果 MyClassA 和 MyClassB 分别定义了一个 shared_ptr 成员变量,用于持有对方的 shared_ptr,并且不允许更改类中引用类型,那么直接使用 weak_ptr 是无法解决循环引用的问题的。在这种情况下,可以考虑引入一个第三方管理对象,该对象负责监控 MyClassA 和 MyClassB 对象的生命周期,并在适当的时候释放它们。

使用第三方管理对象来解决循环引用的问题,具体实现如下:

#include <iostream>
#include <memory>

class MyClassA;
class MyClassB;

class ObjectManager {
private:
    std::weak_ptr<MyClassA> aPtr;
    std::weak_ptr<MyClassB> bPtr;

public:
    void setObjects(const std::shared_ptr<MyClassA>& a, const std::shared_ptr<MyClassB>& b) {
        aPtr = a;
        bPtr = b;
    }

    void checkAndReleaseObjects() {
        if (aPtr.expired() && bPtr.expired()) {
            std::cout << "Both objects expired. Releasing resources." << std::endl;
            // 执行释放资源的操作
        }
    }
};

class MyClassA {
public:
    std::shared_ptr<MyClassB> bPtr;

    MyClassA() {
        std::cout << "MyClassA constructed." << std::endl;
    }

    ~MyClassA() {
        std::cout << "MyClassA destroyed." << std::endl;
    }
};

class MyClassB {
public:
    std::shared_ptr<MyClassA> aPtr;

    MyClassB() {
        std::cout << "MyClassB constructed." << std::endl;
    }

    ~MyClassB() {
        std::cout << "MyClassB destroyed." << std::endl;
    }
};

int main() {
    std::shared_ptr<MyClassA> aPtr = std::make_shared<MyClassA>();
    std::shared_ptr<MyClassB> bPtr = std::make_shared<MyClassB>();

    ObjectManager manager;
    manager.setObjects(aPtr, bPtr);

    // 形成循环引用
    aPtr->bPtr = bPtr;
    bPtr->aPtr = aPtr;

    // 在适当的时候检查并释放对象
    manager.checkAndReleaseObjects();

    return 0;
}
03-21 09:59