• C++ 前置声明错误使用导致的内存泄漏及正确使用方式


    前置声明使用注意:

    1. 只能定义指向前置声明的类的指针或引用。
    2. 以前置声明类型为函数的参数或返回值,该函数只能声明不能定义。

    1. 前置声明误用导致内存泄漏

    看下面的代码:

    #include 
    class A;
    void del(A*a){
    	delete a;
    }
    
    class A{
    public:
    	A(){
    		printf("abc/r/n");
    	}
    	~A(){
    		printf("~abc/r/n");
    	}
    };
    
    int main()
    {
    	A* a= new A;
    	del(A);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    编译和运行都不报错。

    运行结果:

    A()
    
    • 1

    但是并没有调用A的析构函数,为什么?违反了第二点以前置声明类型为函数的参数或返回值,该函数只能声明不能定义

    编译时有警告:

    : In function ‘void del(A*)’:

    :4:9: warning: possible problem detected in invocation of ‘operator delete’ [-Wdelete-incomplete]

        4 | delete a;

            | ^~~~~~~~

    :3:13: warning: ‘a’ has incomplete type

        3 | void del(A* a){

            |             ~~~^

    提示’a’是一个不完整类型。如果不认真看警告,真不容易发现。程序不会有错误,但是会有内存泄漏。

    为什么会这样?
    因为 C++ 编译器自上而下编译源文件,del(A* a)中a是指针,已经知道占据内存的大小,所以可以编译成功。
    若在del中sizeof(A)获取A的大小则会提示“A是不完整类型”的错误而编译失败:

    void del(A* a){
        printf("a size %d\r\n",sizeof(A));
    	delete a;
    }
    
    • 1
    • 2
    • 3
    • 4

    若改成以下这样,是否可以:

    #include 
    class A;
    void del(A*a);
    
    class A{
    public:
    	A(){
    		printf("abc/r/n");
    	}
    	~A(){
    		printf("~abc/r/n");
    	}
    };
    
    void del(A*a){
    	delete a;
    }
    
    int main()
    {
    	A* a= new A;
    	del(A);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    当然是没问题的。

    2. 前置声明常见使用方法

    若两个类互相引用,则需要前置声明。
    下面的代码是否有问题?

    class A;
    
    class B{
    public:
        A a;
    };
    
    class A{
    public:
        B b;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    毫无疑问是有问题的,A和B初始化时递归初始化成员,进入死循环。再一个,class B中A a是不完整类型,违反了第一点只能定义指向前置声明的类的指针或引用。不能直接定义类成员,而应该定义成指针:

    class A;
    
    class B{
    public:
        A* a;
    };
    
    class A{
    public:
        B* b;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    这样就没问题了,虽然class B中定义了A *a,A是不完整类型,但是指针的大小是确定的,并不影响B的定义。

    思考一下,class A改成下面这样可以吗?

    class A{
    public:
        B b;
    };
    
    • 1
    • 2
    • 3
    • 4

    当然是可以的,因为B在A前已经完整定义了。

  • 相关阅读:
    列表变成向量 列表变向量 list vector
    UE5: UpdateOverlap - 从源码深入探究UE的重叠触发
    Nacos 注册中心使用说明
    【MySQL】数据库事务的理解~
    web资产识别
    百度地图高级进阶开发:圆形区域周边搜索地图监听事件(覆盖物重叠显示层级\图像标注监听事件、setZIndex和setTop方法)
    Java课设设计-酒店管理系统
    STM32F10x SPL V3.6.2 集成 FreeRTOS v202112
    [2023毕业设计源码]精品基于PHP实现的剧影评|剧评影评系统[包运行成功]
    数仓为什么要分层
  • 原文地址:https://blog.csdn.net/lwwl12/article/details/133772649