拷贝构造函数的应用场景
//Test.h
class Test
{
public:
Test() {
cout << "Test()" << endl;
m_x = 0;
m_y = 0;
}
Test(int x, int y) {
cout << "Test(int x, int y)" << endl;
m_x = x;
m_y = y;
}
Test(const Test& another) {
cout << "Test(const Test& another)" << endl;
m_x = another.m_x;
m_y = another.m_y;
}
~Test() {
cout << "~Test" << endl;
}
void printT() {
cout << "m_x= " << m_x << " m_y= " << m_y <<endl;
}
void operator=(const Test& another) {
cout << "operator=(const Test& another)" << endl;
m_x = another.m_x;
m_y = another.m_y;
}
private:
int m_x = 0;
int m_y = 0;
};
Test1
//析构函数调用顺序和构造顺序相反,先构造的后析构,类似栈先进后出
void test1() {
//调用有参和拷贝构造函数
Test t1(10, 20);
Test t2(t1); //Test t2 = t1;
}
- 有参构造->拷贝构造->t2析构->t1析构
- 注意析构函数调用顺序和构造顺序相反,先构造的后析构,类似栈先进后出。
Test2
void test2() {
//调用有参、无参和=重载操作符
Test t1(10, 20);
Test t2;
t2 = t1;
}
- 有参构造->非默认无参构造->重载=运算符->t2析构->t1析构
Test3
void func(Test t) { //Test t = test3::t1;
cout << "func begin" << endl;
t.printT();
cout << "func end" << endl;
}
void test3() {
cout << "Test3 begin..." << endl;
Test t1(10, 20);
func(t1);
cout << "Test3 end..." << endl;
}
- 有参构造->func函数实参传递值给形参发生只拷贝调用拷贝构造->func形参t先析构->t1析构
Test4
Test func2() {
Test temp(10, 20);
return temp; //匿名对象=temp 匿名对象.拷贝构造(temp)
}
void test4() {
func2();//当一个函数返回一个匿名对象的时候,函数外部没有任何变量接受它,编译器会直接将这个匿名对象回收,而不是等待整个函数执行完毕再回收
//此时匿名对象已经回收
cout<< "test4 end";
}
- temp有参构造->func2 return时匿名对象拷贝temp调用拷贝构造->temp析构->test4内返回的匿名对象没有被接收立刻析构->test结束
- 当一个函数返回一个匿名对象的时候,函数外部没有任何变量接受它,编译器会直接将这个匿名对象回收,而不是等待整个函数执行完毕再回收
Test5
Test func2() {
Test temp(10, 20);
return temp; //匿名对象=temp 匿名对象.拷贝构造(temp)
}
void test5() {
Test t1 = func2();//会不会触发t1的拷贝构造? 不会,匿名对象直接转正,匿名改个名字
cout << "test5 end" << endl;
}
- 有参构造->func2 return时匿名对象拷贝temp调用拷贝构造->temp析构->test5结束时t1析构
- 和test4的区别在于,test5返回的匿名对象被接收,不会调用析构释放匿名对象,匿名对象已经转正
- return时返回的匿名对象如果有变量接受,则不会再次调用拷贝构造,匿名对象改名后直接转正
Test6
Test func2() {
Test temp(10, 20);
return temp; //匿名对象=temp 匿名对象.拷贝构造(temp)
}
void test6() {
Test t1;
t1 = func2();//func2返回的匿名对象立刻被析构
}
- t1无参构造->temp有参构造->func2返回匿名对象调用拷贝构造->temp析构->重载运算符拷贝->func2返回的匿名对象立刻被析构->t1析构
- 注意t1 = func2()发生的是操作符重载的赋值,匿名对象没有被接收,立刻被析构
PS:test4,5,6发生的返回对象时调用拷贝构造函数产生匿名对象应该避免,返回类对象应该用引用。
NRVO/RVO 返回值优化
结合拷贝构造函数的匿名对象转正展开。
#include <iostream>
class MyObject {
public:
MyObject() {
std::cout << "Constructor" << std::endl;
}
MyObject(const MyObject& other) {
std::cout << "Copy Constructor" << std::endl;
}
~MyObject() {
std::cout << "Destructor" << std::endl;
}
};
MyObject CreateObject() {
//MyObject obj;
return MyObject(); //返回匿名对象
//return obj; // 这里返回临时对象
}
int main() {
MyObject result = CreateObject();
return 0;
}
-
如果被调用的函数返回值是一个函数内局部(临时)对象,不开启NRVO,则会发生一次拷贝构造函数,拷贝给函数调用点的匿名对象,此时直接转正(VS是这样的),开启了就只有一次构造,一次析构。
-
如果被调用的函数返回值是匿名对象,此时RVO优化直接转正(VSDebug或者Release都默认开启,关不了),一次拷贝构造都不会进行。
-
总结来说,无论是返回具名还是匿名对象,完全不优化会经过往函数外传拷贝一次,调用点传给等号左端拷贝一次,如果开启了RVO/NRVO,那一次拷贝构造都不执行,在VS里,Debug下回关闭NRVO,开启RVO,但默认NRVO也只靠背一次,调用单传给等号左端拿一次被自动优化了。
详解去看看,和深入探索C++对象书。
浅拷贝深拷贝
如果类的成员含有指针,就需要显示提供一个拷贝构造函数完成深拷贝,避免指向堆空间已经被析构释放掉。
class Teacher {
public:
Teacher(int id, const char* name) {
this->id = id;
int len = strlen(name);
this->name = (char*)malloc(len + 1);//strlen不计算'\0',但它要占空间
strcpy(this->name, name);
}
~Teacher() {
cout << "~Teacher()"<< endl;
//堆上开辟的空间要给他释放掉
if (name != NULL) {
free(this->name);
this->name = NULL;
}
}
//默认拷贝构造函数,是浅拷贝
//Teacher(const Teacher& another) {
// this->name = another.name;
// this->id = another.id;
//}
//显示提供一个拷贝构造函数完成深拷贝
Teacher(const Teacher& another) {
int len = strlen(another.name);
this->name = (char*)malloc(len + 1);
this->id = another.id;
}
void printT() {
cout << this->id << " " << this->name;
}
private:
int id;
char* name;//对象有指针类型,那就应该显示提供一个拷贝构造函数完成深拷贝
};
void Test() {
Teacher t1(2, "test1");
t1.printT();
Teacher t2(t1);//t2的默认拷贝函数
//t2先析构,t1再析构,但此时t1->name指向的堆空间已经被释放掉,浅拷贝
}
void Test2() {
Teacher t1(2, "test1");
t1.printT();
Teacher t2(t1);//深拷贝,t1和t2是两个对象
}
int main() {
Test();
}
静态成员
class Box {
public:
Box(int l, int w) {
len = l;
width = w;
}
int volume() {
int v = hight * width * len;
cout << hight << endl;
return v;
}
static void ChangHeight(int h) {
hight = h;
}
private:
int len;
int width;
static int hight;
};
int Box::hight = 100;//类外的静态成员变量要在全局定义,不能在Main里
int main() {
Box a(5, 10);
cout << a.volume();
return 0;
}
new和Delete
#include<iostream>
using namespace std;
//C语言 malloc动态分配堆内存空间 malloc是个标准库函数 stdlib.h
void test1() {
int* p = (int*)malloc(sizeof(int));
*p = 10;
if (p != nullptr) {
free(p);
p = nullptr;
}
//开辟数组
int *array_p = (int*)malloc(sizeof(int) * 10);
for (int i = 0; i < 10; i++) {
array_p[i] = 1;
}
if (array_p != nullptr) {
free(array_p);
array_p = nullptr;
}
}
//C++语言 new 动态分配堆内存空间 new是个关键字
int main() {
int* p = new int;
*p = 10;
if (p != nullptr) {
delete p;
p = nullptr;
}
//开辟数组
int* array_p = new int[10];//注意有[]
if (array_p != nullptr) {
delete []array_p; //注意加个[]表示是数组
array_p = nullptr;
}
}
命名空间
引入命名空间可能造成数重载同名,对于名字一样,参数列表还一样的,使用引用声明会报错冲突,而使用引用指示就不会,可以通过::指明调用的是哪一个函数。
#include<iostream>
using namespace std;
namespace test1 {
void test(){
cout << "test1";
};
}
namespace test2 {
using namespace test1;
//using test1::test; 会发生冲突
void test(){
cout << "test2";
}
void doit() {
test2::test(); //test2 test1::test那就是 test1
}
}
int main() {
test2::doit(); //输出test2
return 0;
}