目录
1.未显示定义的拷贝构造函数
1.内置类型完成浅拷贝或值拷贝
代码示例
汇编代码分析
2.自定义类型会调用它的拷贝构造
3.某些情况下不写拷贝构造函数会出现问题
分析
提问:st1和st2谁先析构?
方法1:从栈帧空间分析
方法2:查看反汇编代码
解决方法
4.讨论函数的返回类型:自定义类型或自定义类型的引用
分析
删去function1、function2和function4,只保留function3,单步执行查看问题
步骤分析
承接CD14.【C++ Dev】类和对象(5)文章
1.未显示定义的拷贝构造函数
之前在CD14.【C++ Dev】类和对象(5)文章中提到过拷贝构造函数的第三个特点:若未显式定义,编译器会生成默认的拷贝构造函数,默认的拷贝构造函数对象按字节序完成拷贝,即浅拷贝或值拷贝
1.内置类型完成浅拷贝或值拷贝
代码示例
#include <iostream>
using namespace std;
class Date
{
public:Date(int year = 0, int month = 0, int day = 0){_year = year;_month = month;_day = day;}private:int _year; int _month; int _day;
};int main()
{Date d1(2025,3,13);Date d2(d1);return 0;
}
下断点至return 0;查看d2的成员变量的值:
成功拷贝构造
汇编代码分析
过程调用图:
发现拷贝构造函数Date d2(d1)直接被展开了,没有使用call指令,使用三个不同的寄存器eax、ecx和edx来拷贝构造d2的三个成员变量
2.自定义类型会调用它的拷贝构造
例如用两个栈实现一个队列,例如:
class MyQueue
{
public:MyStack pushst;MyStack popst;
};
MyQueue不需要写它的拷贝构造,直接调用自定义类型MyStack的拷贝构造
3.某些情况下不写拷贝构造函数会出现问题
例如栈,若不写默认拷贝构造函数:
改造CD13的文章的代码
#include <stdlib.h>
typedef int STDataType;
class MyStack
{
public:MyStack(int init_capacity=4){a = (STDataType*)malloc(sizeof(STDataType) * init_capacity);if (a == nullptr){perror("malloc fail");return;}capacity = init_capacity;top = 0;}~MyStack(){free(a);a = nullptr;}STDataType* a;int top;int capacity;
};
测试以下代码:
int main()
{MyStack st1;MyStack st2(st1);return 0;
}
运行结果出错
分析
MyStack类没有写拷贝构造函数,而是编译器自动生成的拷贝构造函数,因此执行MyStack st2(st1);时为浅拷贝或值拷贝,按字节拷贝
则st1和st2的指针a都是同一个值,可以在return 0处下断点验证:
st1.a和st2.a都指向同一块由malloc动态分配的内存空间,画图则为:
则在出类域时, st1和st2都要调用析构函数,则导致同一块动态分配的内存空间被析构两次!
提问:st1和st2谁先析构?
方法1:从栈帧空间分析
栈帧空间的开辟顺序:main函数-->st1-->st2,而析构顺序恰好相反,为st2-->st1
方法2:查看反汇编代码
解决方法
必须手动实现a的拷贝(换句话说,这里为深拷贝),代码如下:
#include <stdlib.h>
#include <cstring>
typedef int STDataType;
class MyStack
{
public:MyStack(int init_capacity=4){a = (STDataType*)malloc(sizeof(STDataType) * init_capacity);if (a == nullptr){perror("malloc fail");return;}capacity = init_capacity;top = 0;}MyStack(const MyStack& st){a = (STDataType*)malloc(sizeof(STDataType) * st.capacity);if (a == nullptr){perror("malloc fail");return;}memcpy(a, st.a,st.top * sizeof(int));capacity = st.capacity;top = st.top;}~MyStack(){free(a);a = nullptr;}STDataType* a;int top;int capacity;
};int main()
{MyStack st1;MyStack st2(st1);return 0;
}
4.讨论函数的返回类型:自定义类型或自定义类型的引用
对比下面4个函数的区别:
MyStack& function1()
{static MyStack st;return st;
}MyStack function2()
{MyStack st;return st;
}MyStack& function3()
{MyStack st;return st;
}MyStack function4()
{static MyStack st;return st;
}
分析
function1:注意到内部定义的st是静态的,存储在静态区中,栈帧销毁时,st不会销毁,因此返回MyStack的引用不会出现问题
function2:注意到内部定义的st存储在function2的栈帧空间中,栈帧销毁时,虽然st会销毁,但返回的是MyStack的拷贝,不会出现问题
function3:注意到内部定义的st存储在function3的栈帧空间中,栈帧销毁时,st会销毁,返回的是st的引用,如果使用返回值会造成非法访问(即访问已经销毁的st),因此function3有问题
function4:注意到内部定义的st是静态的,存储在静态区中,栈帧销毁时,st不会销毁,不会有问题,而且返回MyStack的拷贝,更不会出现问题
可以使用以下的测试代码来验证上述的说法:
#include <stdlib.h>
#include <cstring>
typedef int STDataType;
class MyStack
{
public:MyStack(int init_capacity=4){a = (STDataType*)malloc(sizeof(STDataType) * init_capacity);if (a == nullptr){perror("malloc fail");return;}capacity = init_capacity;top = 0;}MyStack(const MyStack& st){a = (STDataType*)malloc(sizeof(STDataType) * st.capacity);if (a == nullptr){perror("malloc fail");return;}memcpy(a, st.a,st.top * sizeof(int));capacity = st.capacity;top = st.top;}~MyStack(){free(a);a = nullptr;}STDataType* a;int top;int capacity;
};
MyStack& function1()
{static MyStack st;return st;
}MyStack function2()
{MyStack st;return st;
}MyStack& function3()
{MyStack st;return st;
}MyStack function4()
{static MyStack st;return st;
}int main()
{MyStack ret1 = function1();MyStack ret2 = function2();MyStack ret3 = function3();MyStack ret4 = function4();return 0;
}
直接运行会报错:
在ret3处下断点,调试后查看ret1和ret2,没有问题
删去function1、function2和function4,只保留function3,单步执行查看问题
演示视频参见(链接:https://live.csdn.net/v/469330)
CD15文章的function3的演示视频
步骤分析
1.执行到MyStack ret3 = function3();时,先调用function3函数
2.进入function3函数中,来到了MyStack st;句,会调用它的构造函数MyStack(int init_capacity=4)
,可以访问a指向的空间
3.由于st是局部对象,因此return st前,需要调用析构函数,释放动态分配的内存空间,并将a置为空
4.返回st的引用后,调用拷贝构造函数来拷贝构造ret3,在memcpy(a, st.a,st.top * sizeof(int));中虽然a不为空,但st.a为空(之前free掉内存后被手动置空了)
空指针是不能被访问的,因此报错
就算不置空,访问也会出现错误,因为是野指针,比如下图:
*除此之外,function2比function3更消耗时间,因为function2是传值返回,需要拷贝数据
结论
不能返回局部对象的引用!