智能指针常见错误用法

编程难

共 4774字,需浏览 10分钟

 · 2020-12-07

前言

智能指针的出现大大减轻了 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 pNewData(pRawData);
        pNewData->DoSomething();
    }

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

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

#pragma once
#include 

#include "common.h"

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

    static std::shared_ptr GetData()
    
{
        return std::make_shared();
    }
    static void Entry()
    
{
        auto pTest = std::make_shared();

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

        // code below is good
        //Use(std::dynamic_pointer_cast(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();
        return pData.get();
    }

    static bool GetRawPointer(Common::CDerived* & pReturnedData)
    
{
        auto pData = std::make_shared();
        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 pData)
    
{
        pData->DoSomething();
    }

    static void Entry()
    
{
        auto pTest = std::make_shared();
        Use(std::shared_ptr(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 pData)
    
{
        pData->DoSomething();
    }

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

        Use(pData);
    }
}

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

#pragma once
#include 
#include 
#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> children;
    };

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

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

        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

总结

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


浏览 46
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报