c++ 若不想使用编译器自动生成的函数, 就该明确拒绝,为多态基类声明virtual 析构函数

条款6 若不想使用编译器自动生成的函数, 就该明确拒绝

在C++中,编译器会自动生成一些函数,例如默认的构造函数、拷贝构造函数、赋值运算符等。这些函数通常用于创建对象、初始化对象和实现对象之间的值传递。

然而,在某些情况下,开发者可能希望禁用这些自动生成的函数,而使用自己编写的函数。例如,当一个类有特殊的初始化逻辑时,开发者可能希望禁用默认构造函数,而使用一个自定义的构造函数。

要拒绝编译器自动生成的函数,需要在类定义中使用= delete关键字。例如:
如:

template<typename  T>
class NamedObject{
public:
    NamedObject(const char *name, const T&value):nameValue(name),objectValue(value){}
    NamedObject(const string &name, const T&value):nameValue(name),objectValue(value){}
    NamedObject()=delete;
    NamedObject & operator=(const NamedObject &obj) = delete;
private:
    string nameValue;
    T objectValue;
};

条款7 为多态基类声明virtual 析构函数

在C++中,当一个类继承自一个基类时,如果基类有一个或多个虚函数,那么派生类中的同名函数必须为虚函数。这是为了确保基类和派生类之间的对象能够正确地销毁。
要为多态基类声明virtual 析构函数,需要在基类中添加一个名为~virtualDtor的析构函数,并使用virtual关键字。
基类析构函数没有声明为virtual, 产生泄漏, 如:

#include <iostream>
#include <stdlib.h>
#include <string.h>

namespace experiment1{
    class TimeKeeper1{
    public:
        TimeKeeper1() = default;
        ~TimeKeeper1() = default;
    };

    class AtomicClock: public TimeKeeper1{

    };

    class WaterClock: public TimeKeeper1{
    public:
        WaterClock(){
            m_msg = (char *)malloc(10);
            memset(m_msg, 0, sizeof(m_msg));
        }
        ~WaterClock(){
            if(m_msg != NULL){
                free(m_msg);
            }
        }
    private:
        char *m_msg;
    };

    class WristWatch : public TimeKeeper1{

    };

    TimeKeeper1 * getTimeKeeper(){
        TimeKeeper1 * ptk = new WaterClock();
        return ptk;
    }


    void test1(){
        TimeKeeper1 *ptk = getTimeKeeper();
        delete ptk;
    }
}

int main(){
    experiment1::test1();
 return 0;
}

编译之后, 使用valgrind 查看
g++ -g test07.cpp -o test
valgrind --tool=memcheck --leak-check=yes --log-file=trace.log ./test
vi trace.log

  1 ==248== Memcheck, a memory error detector
  2 ==248== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
  3 ==248== Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info
  4 ==248== Command: ./test
  5 ==248== Parent PID: 8
  6 ==248==
  7 ==248== error calling PR_SET_PTRACER, vgdb might block
  8 ==248==
  9 ==248== HEAP SUMMARY:
 10 ==248==     in use at exit: 10 bytes in 1 blocks
 11 ==248==   total heap usage: 3 allocs, 2 frees, 72,722 bytes allocated
 12 ==248==
 13 ==248== 10 bytes in 1 blocks are definitely lost in loss record 1 of 1
 14 ==248==    at 0x483C855: malloc (vg_replace_malloc.c:381)
 15 ==248==    by 0x1092DB: experiment1::WaterClock::WaterClock() (test07.cpp:22)
 16 ==248==    by 0x10920A: experiment1::getTimeKeeper() (test07.cpp:39)
 17 ==248==    by 0x10922A: experiment1::test1() (test07.cpp:45)
 18 ==248==    by 0x109254: main (test07.cpp:51)
 19 ==248==
 20 ==248== LEAK SUMMARY:
 21 ==248==    definitely lost: 10 bytes in 1 blocks
 22 ==248==    indirectly lost: 0 bytes in 0 blocks
 23 ==248==      possibly lost: 0 bytes in 0 blocks
 24 ==248==    still reachable: 0 bytes in 0 blocks
 25 ==248==         suppressed: 0 bytes in 0 blocks
 26 ==248==
 27 ==248== For lists of detected and suppressed errors, rerun with: -s
 28 ==248== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

通过 valgrind 看出, m_msg = (char *)malloc(10); 申请的内存没有被释放。
原因: 通过GetTimeKeeper 返回的指针是一个基类指针,销毁基类指针则会取基类的部分(调用基类的析构函数)
官方: C++明白指出,当derived class对象经由一个base class指针被删除,而其base class带一个 non-virtual函数
其结果就是未定义-实际执行下来发生的就是对象的 derived 成分没被销毁
解决: 给base class 设置一个 virtual 析构函数即可
~TimeKeeper1() = default; 添加上virtual 再次查看

 ==264== Memcheck, a memory error detector
  2 ==264== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
  3 ==264== Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info
  4 ==264== Command: ./test
  5 ==264== Parent PID: 8
  6 ==264==
  7 ==264== error calling PR_SET_PTRACER, vgdb might block
  8 ==264==
  9 ==264== HEAP SUMMARY:
 10 ==264==     in use at exit: 0 bytes in 0 blocks
 11 ==264==   total heap usage: 3 allocs, 3 frees, 72,730 bytes allocated
 12 ==264==
 13 ==264== All heap blocks were freed -- no leaks are possible
 14 ==264==
 15 ==264== For lists of detected and suppressed errors, rerun with: -s
 16 ==264== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

通过valgrind检测 m_msg = (char *)malloc(10)申请的内容得以释放。

任何 class 带有virtual函数都几乎确定应该有一个 virtual 析构函数,
没有理由地把所有 class 的析构函数设置为 virtual的行为是错误的。

namespace experiment2 {
// 设置虚函数的目的是为了能实现多态,因此可以肯定有派生类的存在。
//如下所示,我们随意在一个class将其析构函数设置为 virtual,
/*virtual的声明会使得 Point的结构膨胀,安插指向虚函数表的 vptr
 * (32bit计算机体系Point空间为 64bits,因为 vptr的存在变为 96bits)
 */
    class Point1 {
    private:
        int x_;
        int y_;
    public:
        Point1(int xcoord, int ycoord) {

        };
        ~Point1() {

        };
    };
    class Point2 {
    private:
        int x_;
        int y_;
    public:
        Point2(int xcoord, int ycoord) {

        };
        virtual ~Point2() {

        };
    };

}
int main(){
    using namespace experiment2;
    Point1 pt1(1,2);
    Point2 pt2(1,2);
    cout<<"pt1 size:"<<sizeof(pt1)<<endl;
    cout<<"pt2 size:"<<sizeof(pt2)<<endl;
 return 0;
}

编译执行结果:

src$ g++ -g test07.cpp -o test
src$ ./test
pt1 size:8
pt2 size:16
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值