需要析构函数的类也需要拷贝和赋值操作

需要析构函数的类也需要拷贝和赋值操作

当我们决定一个类是否要定义它自己版本的拷贝控制成员时,一个基本原则是首先确定这个类是否需要一个析构函数。通常,对析构函数的需求要比对拷贝构造函数或赋值运算符的需求更为明显。如果这个类需要一个析构函数,我们几乎可以肯定它也需要一个拷贝构造函数和一个拷贝赋值运算符。
例如,我们的 HasPtr 类在构造函数中分配动态内存,合成析构函数不会 delete 一个指针数据成员。因此,此类需要定义一个析构函数来释放构造函数分配的内存。
应该怎么做可能还有点儿不清晰,但基本原则告诉我们,HasPtr 也需要一个拷贝构造函数和一个拷贝赋值运算符。
如果我们为 HasPtr 定义一个析构函数,但使用合成版本的拷贝构造函数和拷贝赋值运算符,考虑会发生什么:

class HasPtr
{
public:
    HasPtr(const std::string &s = std::string()):
        ps(new std::string(s)), i(0) {}
    ~HasPtr() { delete ps; }
    // 错误: HasPtr 需要一个拷贝构造函数和一个拷贝赋值运算符
    //其他成员的定义,如前
}

在这个版本的类定义中,构造函数中分配的内存将在 HasPtr 对象销毁时被释放。但不幸的是,我们引入了一个严重的错误!这个版本的

类使用了合成的拷贝构造函数和拷贝赋值运算符。这些函数简单拷贝指针成员,这意味着多个 HasPtr 对象可能指向相同的内存:

HasPtr f(HasPtr hp)     //HasPtr是传值参数,所以将被拷贝
{
    HasPtr ret = hp;    //拷贝给定的HasPtr
    //处理ret
    return ret;         //ret和hp被销毁
}

当 f 返回时,hp 和 ret 都被销毁,在两个对象上都会调用 HasPtr 的析构函数。此析构函数会 delete ret 和 hp 中的指针成员。但这两个

对象包含相同的指针值。此代码会导致此指针被 delete 两次,这显然是一个错误。将要发生什么是未定义的。

此外,f 的调用者还会使用传递给 f 的对象:

HasPtr p("some values");
f(p);              //当f结束时,p.ps 指向的内存被释放
HasPtr q(p);       //现在p和q都指向无效内存!

p(以及q)指向的内存不再有效,在 hp(或 ret!)销毁时它就被归还给系统了。

如果一个类需要自定义析构函数,几乎可以肯定它也需要自定义拷贝赋值运算符和拷贝构造函数。

该文章会更新,欢迎大家批评指正。

推荐一个零声学院免费公开课程,个人觉得老师讲得不错,
分享给大家:Linux,Nginx,ZeroMQ,MySQL,Redis,
fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,
TCP/IP,协程,DPDK等技术内容,点击立即学习:
服务器课程:C++服务器

你可能感兴趣的:(CC++编程要点,c++)