c++ primer 15.41习题解答

本文回顾了作者早期关于C++ Primer习题15.41的博客,重点讲解了如何避免深拷贝问题、内存泄漏和析构函数的虚拟性,以及类定义顺序的重要性。通过实例展示了如何正确使用deletePtr和类之间的依赖关系。
摘要由CSDN通过智能技术生成


前言

这是我两年前第一个CSDN博客文章, 文章中我犯了所有初学者都会犯的问题, 博客文章没有起因, 没有说明解决什么问题, 弄得人一头雾水, 现在回过头, 需要从头梳理, 是的, 我自己都不知道当时自己在说什么.


一、习题内容

c++ primer 15.41习题, 重新实现你的类,这次使用指向Query_base的内置指针而非shared_ptr

请注意,做出上述改动后将不能再使用合成的拷贝控制成员。

二、坑的地方

现在回顾起来, 坑的地方是如何实现share_ptr的功能, 原程序设计, 使用share_ptr, 就不必过于在意所谓深拷贝和浅拷贝问题, 智能指针直接接管, 没有浅拷贝问题, 以及后续的内存泄漏或多次delete问题.

还有我当时并没注意一个细节, 导致后续乌龙, 就是面向对象, 一定要把析构函数做virtual函数, 否则会导致内存泄漏.

另外, 类的书写顺序也很重要, 先声明, 后定义, 互相有依赖的, 被依赖类要最先声明定义, 没有完整声明的类, 可以成为另一个类的成员, 但需要是指针形式, 否则, 就是使用不完整类报错.


总结


以下是原文内容:

c++ primer 15.41 坑,大坑。

github 的答案是掠过的😱。这真是太不幸了,没有提示做这道题,简直自虐。

参考13章的代码,需要自己计算指针引用数量,但这还不够。因为15.41这个结构和13章结构不同。此题结构是指针嵌套!直接粗鲁的delete,会导致嵌套指针内存泄漏!!!

对付的办法也比较简单,就是在指针引用为0时,强制进行~QueryBase()析构。

问题是QueryBase在Query之后定义,需要用个友元函数返回一下。

强制QueryBase析构的妙处是,进行Query的递归析构。这样就可以一层一层的析构,不会删了外层指针,把里面的指针丢失导致内存泄漏。

#ifndef DELETEPTR
#define DELETEPTR

#include <string>
#include <iostream>

struct Base;

struct wordBase;

struct deletePtr
{
    deletePtr(const deletePtr &rhs)
        : basePtr(rhs.basePtr), cnt(rhs.cnt) { ++(*cnt); }

    deletePtr(const std::string &rhs);

    ~deletePtr();

private:
    friend deletePtr operator~(const deletePtr &);
    friend deletePtr operator&(const deletePtr &, const deletePtr &);

    deletePtr(Base *iter) : basePtr(iter), cnt(new int(1)) {}

    Base *basePtr = nullptr;
    int *cnt = nullptr;
};

struct Base
{
    virtual ~Base()
    {
        std::cout << "virtual ~Base()" << std::endl;
    };
};

deletePtr::~deletePtr()
{
    --(*cnt);
    if (*cnt == 0)
    {
        delete basePtr;
        delete cnt;
        std::cout << "~deletePtr()" << std::endl;
    }
}

struct wordBase : public Base
{
    wordBase(const std::string &rhs) : word(rhs) {}

    ~wordBase()
    {
        std::cout << "~wordBase()" << std::endl;
    }

private:
    std::string word;
};

deletePtr::deletePtr(const std::string &rhs)
    : basePtr(new wordBase(rhs)), cnt(new int(1)){};

struct notDeletePtr : public Base
{
    ~notDeletePtr()
    {
        std::cout << "~notDeletePtr()" << std::endl;
    }

private:
    friend deletePtr operator~(const deletePtr &);

    notDeletePtr(const deletePtr &rhs) : delPtr(rhs) {}

    deletePtr delPtr;
};

deletePtr operator~(const deletePtr &rhs)
{
    Base *Ptr = new notDeletePtr(rhs);
    return Ptr;
}

struct binBase : public Base
{
    ~binBase()
    {
        std::cout << "~binBase()" << std::endl;
    }

protected:
    binBase(const deletePtr &rhsOne, const deletePtr &rhsTwo, std::string s)
        : lhs(rhsOne), rhs(rhsTwo), op(s) {}

    deletePtr lhs;
    deletePtr rhs;
    std::string op;
};

struct andDeletePtr : public binBase
{
    andDeletePtr(const deletePtr &lhs, const deletePtr &rhs)
        : binBase(lhs, rhs, "&") {}

    ~andDeletePtr()
    {
        std::cout << "~andDeletePtr()" << std::endl;
    }

private:
    friend deletePtr operator&(const deletePtr &, const deletePtr &);
};

deletePtr operator&(const deletePtr &lhs, const deletePtr &rhs)
{
    Base *ptr = new andDeletePtr(lhs, rhs);
    return ptr;
}

#endif

以上是示例代码,我后来发现,析构delete 一个类指针的时候,如果要删除的类没有完成定义,析构函数一定要将定义放在这个类完成定义之后!!!

否则不能自动的进行递归析构,也就是不能真正的删除这个指针。

所以现在可以说,可以把这个坑填一填了,总结如下:

1,请务必理解RAII的设计原理,争取做到谁开出的资源谁删除,用智能指针,或引用计数追踪,在析构函数delete。

2,类的构建规则要服从:

类声明1,2,3.  

类定义实现1,2,3. 

类的成员函数在类的定义中声明1,2,3.

在所有相关引用类的定义实现完成后,定义实现类成员函数1,2,3 

否则很可能出现因使用不完整类导致的错误,比如内存泄漏。

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不停感叹的老林_<C 语言编程核心突破>

不打赏的人, 看完也学不会.

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值