智能指针避坑指南——几种常见的错误用法

简介: 智能指针避坑指南——几种常见的错误用法

前言

智能指针的出现大大减轻了 C++ 程序员的心理负担(最少对于我是这样的),不用再时时刻刻担心一个 new 出来的指针是否被 delete 的问题了。虽然智能指针很强大,但是如果用不好,还是会导致各种各样的问题。最近,在项目里看到了几种智能指针的典型错误用法。有的严重,有的轻,有的问题在研发阶段并没有立刻暴露出来,埋下了一颗定时炸弹。趁着这个机会,总结一下几种常见的错误用法。希望对各位小伙伴儿有帮助。

说明:智能指针有很多种,标准库提供了几种,比如最常用的 shared_ptrunique_ptr, 比较少用的 weak_ptr 以及被废弃的 auto_ptr。项目代码里还有一种基于引用计数的智能指针。本文只列举几种常见的错误用法,不会深入到各种智能指针的实现。如果想深刻理解智能指针的用法,一定要看源码。

错误用法

在列举各种错误用法之前,想说明一点:这些错误都是实际遇到过的,并不是凭空想出来的。所以,即使有些示例代码错的是那么明显,也请不要轻视。

1. 同一指针交给两个智能指针管理导致二次释放

namespace case1
{
    /*
    * pRawData is already managed by a shared_ptr (pData in function #Entry()),
    * in function #Use(), another shared_ptr (pNewData) manages the raw pointer too.
    * now, we have two shared_ptr manage the same raw pointer. Oops, double free!
    */
    static void Use(Common::CDerived* pRawData)
    {
        std::shared_ptr<Common::CDerived> pNewData(pRawData);
        pNewData->DoSomething();
    }

    static void Entry()
    {
        auto pData = std::make_shared<Common::CDerived>();
        Use(pData.get());
    }
}

2. 错误的动态转换导致的二次释放

#pragma once
#include <memory>

#include "common.h"

namespace case2
{
    static void Use(std::shared_ptr<Common::CDerived> pData)
    {
        pData->DoSomething();
    }

    static std::shared_ptr<Common::CBase> GetData()
    {
        return std::make_shared<Common::CDerived>();
    }
    static void Entry()
    {
        auto pTest = std::make_shared<Common::CDerived>();

        // Oops, double free
        std::shared_ptr<Common::CBase> pData = GetData();
        auto pRawData = pData.get();
        Common::CDerived* pDerived = dynamic_cast<Common::CDerived*>(pRawData);
        if (pDerived)
        {
            Use(std::shared_ptr<Common::CDerived>(pDerived));
        }

        // code below is good
        //Use(std::dynamic_pointer_cast<Common::CDerived>(pData));
    }
}

3. 返回智能指针管理的原生指针


namespace case3
{
    /*
    * after #ReturnRawPointer() and #GetRawPointer() returned, shared_ptr's destructor is called,
    * then the returned raw pointer points to a deleted address. bang!
    */
    static Common::CDerived* ReturnRawPointer()
    {
        auto pData = std::make_shared<Common::CDerived>();
        return pData.get();
    }

    static bool GetRawPointer(Common::CDerived* & pReturnedData)
    {
        auto pData = std::make_shared<Common::CDerived>();
        pReturnedData = pData.get();
        return (pReturnedData != nullptr);
    }

    static void Entry()
    {
        auto pData = ReturnRawPointer();
        pData->DoSomething();

        pData = nullptr;
        GetRawPointer(pData);
        pData->DoSomething();
    }
}

4. 类中的成员变量指针交给外部智能指针管理

namespace case4
{
    /*
    * CTest::pData is managed by CTest, but when call #Use(), it is used to construct a shared_pt,
    * then it is also managed by a shared_ptr. double free!
    */
    class CTest
    {
    public:
        CTest() { pData = new Common::CDerived(); }

        ~CTest() { delete pData; }

        Common::CDerived* pData;
    };

    /* this function need a shared_ptr, this is ok.*/
    static void Use(std::shared_ptr<Common::CDerived> pData)
    {
        pData->DoSomething();
    }

    static void Entry()
    {
        auto pTest = std::make_shared<CTest>();
        Use(std::shared_ptr<Common::CDerived>(pTest->pData));
    }
}

5. 栈变量的地址交给智能指针管理

namespace case5
{
    /*
    * data is on stack, after managed by a shared_ptr, it will be deleted.
    * we CAN NOT delete an address on stack!
    */
    static void Use(std::shared_ptr<Common::CDerived> pData)
    {
        pData->DoSomething();
    }

    static void Entry()
    {
        // well, I have simplify this error quite much.
        Common::CDerived data;
        auto pData = std::shared_ptr<Common::CDerived>(&data);

        Use(pData);
    }
}

6. 循环引用导致的内存泄漏

#pragma once
#include <memory>
#include <vector>
#include "common.h"

namespace case6
{
    class CPerson
    {
    public:
        CPerson() { printf(__FUNCTION__ "\n"); }
        virtual ~CPerson() { printf(__FUNCTION__ "\n"); }
    };

    class CParent : public CPerson
    {
    public:
        CParent() { printf(__FUNCTION__ "\n"); }
        virtual ~CParent() { printf(__FUNCTION__ "\n"); }
        std::vector<std::shared_ptr<CPerson>> children;
    };

    class CChild : public CPerson
    {
    public:
        CChild() { printf(__FUNCTION__ "\n"); }
        virtual ~CChild() { printf(__FUNCTION__ "\n"); }
        std::shared_ptr<CPerson> parent;
    };

    static void Entry()
    {
        // Oops, no one will be died then.
        std::shared_ptr<CParent> parent = std::make_shared<CParent>();
        std::shared_ptr<CChild> child1 = std::make_shared<CChild>();
        std::shared_ptr<CChild> child2 = std::make_shared<CChild>();
        std::shared_ptr<CChild> child3 = std::make_shared<CChild>();

        parent->children.push_back(child1);
        parent->children.push_back(child2);
        parent->children.push_back(child3);
        child1->parent = parent;
        child2->parent = parent;
        child3->parent = parent;
    }
}

示例代码

完整的示例代码下载地址

CSDN:https://download.csdn.net/download/xiaoyanilw/13203123

百度云:https://pan.baidu.com/s/1dFUKevDXJZfja3HyO-jazg 提取码: 8ra8

总结

以上示例代码虽然有的看起来非常不可思议,这是我简化后的结果,在实际代码中经常以另外一种形式出现,一不小心就容易中招。

相关文章
|
6月前
|
安全 程序员 编译器
C++中的RAII(资源获取即初始化)与智能指针
C++中的RAII(资源获取即初始化)与智能指针
82 0
|
6月前
|
安全 程序员 C++
C++中的智能指针:从原始指针到现代内存管理
C++中的智能指针:从原始指针到现代内存管理
49 0
|
6月前
|
C++
C++:一文读懂智能指针
C++:一文读懂智能指针
99 0
|
6月前
|
安全 C++ 容器
C++中的智能指针:自动内存管理的利器
C++中的智能指针:自动内存管理的利器
83 0
|
5月前
|
测试技术 C++ 开发者
智慧指针是什么以及具体用法
智慧指针是什么以及具体用法
38 2
|
6月前
|
存储
指针用法及分类
指针用法及分类
|
6月前
|
存储 安全 程序员
【C++ 包装器类 智能指针】完全教程:std::unique_ptr、std::shared_ptr、std::weak_ptr的用法解析与优化 — 初学者至进阶指南
【C++ 包装器类 智能指针】完全教程:std::unique_ptr、std::shared_ptr、std::weak_ptr的用法解析与优化 — 初学者至进阶指南
246 0
|
6月前
|
算法 Java
快慢指针该如何操作?本文带你认识快慢指针常见的三种用法及在链表中的实战
快慢指针该如何操作?本文带你认识快慢指针常见的三种用法及在链表中的实战
103 0
|
6月前
|
存储 Rust C++
C++智能指针
【2月更文挑战第14天】介绍C++智能指针
49 0
|
6月前
|
算法 安全 C++
C++ Effective Modern Pointer (智能指针模块)
C++ Effective Modern Pointer (智能指针模块)