前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >识别C++代码质量的诀窍,在这里……

识别C++代码质量的诀窍,在这里……

作者头像
用户6557940
发布2022-07-24 16:49:06
1970
发布2022-07-24 16:49:06
举报
文章被收录于专栏:Jungle笔记Jungle笔记

在知乎上关注了一些C++相关的话题。看到一个答主说:

也就是说,如果一个class有析构函数,并且析构函数有释放资源的操作,那么作者应该对拷贝构造和拷贝赋值函数有所处理,要么提供正确实现,要么delete。否则,这个class不是一个完整安全的设计。

这个回答所在的问题是“C++后台开发有哪些练基础的开源项目?”。提问者应该是想通过一些开源项目来学习和实践C++。很多回答都给出了不少Github上的优秀项目的链接。

然而这个答主却只给出了这么简短的回答。

这个答主是:陈硕。

好比每一个学习C++的人都知道侯捷大师一样,每个C++开发者想必也都听说过陈硕。(如果没有,那么看到这里你也听说过了)。比起Github上纷繁复杂的开源项目,陈硕大佬说,项目太多,你要能够找到真正好的项目来学习。如何判断一个项目的优劣?答案就是上面那段话。

简单解释下为什么析构函数里有释放资源的操作后,就得处理拷贝构造和拷贝赋值。

比如下面一个class A:

代码语言:javascript
复制
class A
{
public:
    A(char ia = ' ', short ib = 0, int ic = 0) : a(ia), b(ib), c(ic) {}

private:
    char a;
    short b;
    int c;
};

对于这个class,不需要析构函数,因为class中没有涉及申请内存资源的操作,对于基础的数据类型,对象析构时也不涉及释放资源的操作。

但如果类中涉及到申请资源的操作,如果没有自己定义拷贝构造函数和拷贝赋值函数,编译器默认生成的版本都是浅拷贝。那么当源对象析构过程释放掉资源时(delete ptr),浅拷贝生成的对象里的ptr指向的资源实际已经被回收了,那么拷贝的对象中的ptr实际指向的资源已经不存在,当再次析构时将引发异常。比如下面的class B:

代码语言:javascript
复制
class B
{
public:
    B() {}
    B(int ilen, const char *iname) : len(ilen)
    {
        name = new char[len];
        memcpy(name, iname, len);
    }
    ~B()
    {
        delete[] name;
    }
    void print()
{
        cout << "len = " << len << "  name = " << name << endl;
    }

private:
    char *name;
    int len;
};

int main(int argc, char *argv[])
{
    B b1(4, "abc");
    b1.print();

    B b2 = b1;
    b2.print();

    return 0;
}

该程序退出时会引发异常,因为退出时,两个栈对象b1和b2将依次调用析构函数。B的析构函数中会释放掉name指针指向的内存空间。但由于没有提供B的拷贝构造函数的定义,因此下面的代码将引发浅拷贝:

代码语言:javascript
复制
B b2 = b1;

因此b2和b1中的name实际指向同一块资源。当b1析构时,b1和b2指向的资源已经被释放。当b2析构时,再去delete一个已经不存在的资源,引发异常。

(当然,如果B中没有定义析构函数,或者析构函数中没有去delete,那么上述代码也不会出现问题。但是new的内存没有释放,最后将交给OS释放,这也不是一种好的编码,更不是一种好的编程习惯!)

解决办法是,要么我完全禁止拷贝构造和拷贝赋值的行为:

代码语言:javascript
复制
class B
{
public:
    B() {}
    B(int ilen, const char *iname) : len(ilen)
    {
        name = new char[len];
        memcpy(name, iname, len);
    }
    B(const B& other) = delete;
    B& operator=(const B& other) = delete;
    ~B()
    {
        delete[] name;
    }

private:
    char *name;
    int len;
};

要么就提供拷贝构造和拷贝赋值的实现:

代码语言:javascript
复制
class B
{
public:
    B() {}
    B(int ilen, const char *iname) : len(ilen)
    {
        name = new char[len];
        memcpy(name, iname, len);
    }
    B(const B& other)
    {
        len = other.len;
        name = new char[len];
        memcpy(name, other.name, len);
    }
    B& operator=(const B& other) 
    {
        if(this != &other){
            delete []name;
            len = other.len;
            name = new char[len];
            memcpy(name, other.name, len);
        }
        return *this;
    }
    ~B()
    {
        delete[] name;
    }

private:
    char *name;
    int len;
};

所以万丈高楼平地起,纷繁复杂的项目令人眼花缭乱,但稳固的代码基础才是基石。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-10-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Jungle笔记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档