CppBoost(对象优化)

在这里插入图片描述

对象使用背后调用的方法

#include<iostream>

using namespace std;

class Test
{
    public:
        Test(int a = 10):ma(a)  {cout<<"Test(int)"<<endl;}
        ~Test(){cout<<"~Test()"<<endl;}
        Test(const Test& t):ma(t.ma) {cout<<"Test(const Test& t)"<<endl;}
        Test& operator=(const Test& t) 
        {
            cout<<"operator="<<endl; 
            ma = t.ma;
            return *this;
        }

    private:
        int ma;
};

int main()
{
    Test t1;	//构造	
    Test t2(t1);    //拷贝构造
    Test t3 = t1;	//拷贝构造

    //Test(20) 显式生成临时对象 生存周期:所在的语句 
    Test t4 = Test(20);
    /*  调用过程? 先调用构造生成临时对象,再调用拷贝构造,析构? */

    return 0;
}

输出结果:
在这里插入图片描述

从输出结果我们可以看到,C++编译器对于对象构造做出的优化:用临时对象生成新对象的时候,临时对象就不产生了,直接构造新对象就可以了,即 Test t4 = Test(20) 等价于 Test t4(20)

int main()
{
    Test t1;
    Test t2(t1);    
    Test t3 = t1;

    //Test(20)  
    Test t4 = Test(20);

    t4 = t1;	//拷贝构造
    t4 = Test(30);		

    return 0;
}

在这里插入图片描述
用临时对象给已有对象赋值会先创建临时对象,再调用赋值运算符重载

显式 or 隐式 生成临时对象:

int main()
{
    Test t4;
	//显式生成临时对象
    t4 = Test(30);
    t4 = (Test)30; 
	//隐式生成临时对象
    t4 = 30; 

    return 0;
}

在这里插入图片描述

指针 引用 赋值临时对象:

int main()
{
    Test *p = &Test(30);

    const Test &ref = Test(40);

    return 0;
}

在这里插入图片描述
无法对临时对象取地址,临时对象的生命周期只在所在语句,出语句临时对象就会被析构

在这里插入图片描述
引用是变量的别名,可以用临时对象赋值给常对象

示例:

#include<iostream>

using namespace std;

class Test
{
    public:
        Test(int a = 5,int b = 5): ma(a), mb(b) 
        {
            cout << "Test(int,int)" << endl;
        }
        ~Test()
        {
            cout << "~Test()" << endl;
        }
        Test(const Test &src): ma(src.ma), mb(src.mb)
        {
            cout << "Test(const Test &)" << endl;
        }
        void operator=(const Test &src)
        {
            ma = src.ma;
            mb = src.mb;
            cout << "operator=" << endl;
        }

    private:
        int ma;
        int mb;
};

Test t1(10, 20);    // 1.Test(int,int)
int main()
{
    Test t2(20,20);     // 3.Test(int,int)
    Test t3 = t2;       // 4.Test(const Test &)
    static Test t4 = Test(30, 40);      // 5.Test(int,int)
    t2 = Test(40, 50);      // 6.Test(int,int)    operator=  ~Test()
    t2 = (Test)(50, 60);       // 7.Test(int,int)   operator=     ~Test()
    t2 = 60;               // 8.Test(int,int)    operator=     ~Test()
    Test *p1 = new Test(70, 80);    // 9.Test(int,int)
    Test *p2 = new Test[2];                 // 10.Test(int,int)  Test(int,int)
    const Test &p3 = Test(90, 100);             // 11.Test(int,int)
    delete p1;              // 12.~Test()   free(p1)
    delete[] p2;            // 13.~Test()   ~Test()    free(p2)
}
Test t5(100,100);   // 2.Test(int,int)

运行一下~

在这里插入图片描述

函数调用过程中对象背后调用的方法

参考以下示例代码,分析其函数调用过程

#include<iostream>

using namespace std;

class Test
{
public:
    Test(int a = 10):a_(a)  
    {
        cout << "Test()" << endl; 
    }
    ~Test()     
    {
        cout << "~Test()" << endl;
    }
    Test(const Test& t):a_(t.a_)
    {
        cout << "Test(const Test& t)" << endl;
    }
    void operator=(const Test& t)
    {
        cout << "operator=(const Test& t)" << endl;
        a_ = t.a_;
    }
    int getA() const { return a_; }

private:
    int a_;
};

Test GetObj(Test t)
{
    int val = t.getA();
    Test tmp(val);
    return tmp;
}

int main()
{
    Test t1;
    Test t2; 
    t2 = GetObj(t1);
    
    return 0;
}

在这里插入图片描述
首先main函数里的 t1、t2 调用构造函数,GetObj(t1) 调用 t1 给 GetObj函数初始化形参Test t,接下来tmp调用构造函数,return tmp 构造了一个临时对象,GetObj函数结束,进行局部对象析构,先对tmp进行析构,然后对形参 t 进行析构;赋值运算符重载,使用临时对象对 t2 赋值,接着临时对象析构,t2、t1析构

三条对象优化的规则

- 函数参数传递过程中,对象优先按引用传递,不要按值传递
- 函数返回对象的时候,应该优先返回一个临时对象,而不要返回一个定义过的对象
- 接收返回值是对象的函数调用的时候,优先按初始化的方式接收,不要按赋值的方式接收

下面通过代码示例的方式,分析逐条规则的含义:
函数参数传递过程中,对象优先按引用传递,不要按值传递

Test GetObj(Test &t)
{
    int val = t.getA();
    Test tmp(val);
    return tmp;
}

int main()
{
    Test t1;
    Test t2; 
    t2 = GetObj(t1);

    return 0;
}

在这里插入图片描述

Test GetObj(Test &t)
{
    int val = t.getA();
    Test tmp(val);
    return tmp;
}

int main()
{
    Test t1;
    Test t2; 
    t2 = GetObj(t1);

    return 0;
}

在这里插入图片描述
第一段代码 Test 函数形参采用的是对象的值传递,第二段采用的是引用传递,从输出结果来看,采用引用传递的方式输出少了 一段拷贝赋值运算符重载;这是由于采用值传递会先拷贝一份传入参数的数据,用于构建 Test 中的临时对象,如果采用引用传递,可以避免此操作的开销

函数返回对象的时候,应该优先返回一个临时对象,而不要返回一个定义过的对象

Test GetObj(Test &t)
{
    int val = t.getA();
    Test tmp(val);
    return tmp;
}

int main()
{
    Test t1;
    Test t2; 
    t2 = GetObj(t1);

    return 0;
}
Test GetObj(Test &t)
{
    int val = t.getA();
    return Test(val);
}

int main()
{
    Test t1;
    Test t2; 
    t2 = GetObj(t1);

    return 0;
}

在这里插入图片描述

在这里插入图片描述
在 Test 函数编写过程中,先定义临时对象在返回会先调用构造函数,再调用析构;而将临时对象放在 return 中定义,编译器会用构造的临时对象去拷贝构造新对象,会直接构造新对象,优化了临时对象产生的问题

接收返回值是对象的函数调用的时候,优先按初始化的方式接收,不要按赋值的方式接收

Test GetObj(Test &t)
{
    int val = t.getA();
    return Test(val);
}

int main()
{
    Test t1;
    Test t2; 
    t2 = GetObj(t1);

    return 0;
}
Test GetObj(Test &t)
{
    int val = t.getA();
    return Test(val);
}

int main()
{
    Test t1;    
    Test t2 = GetObj(t1);
    
    return 0;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

对比分析可以看到编译器以同样地方式进一步作出优化,临时对象不生成了,直接调用两次构造、析构后返回

CMyString代码问题

#include<iostream>
#include<cstring>

using namespace std;

class CMyString
{
public:
    CMyString(); 
    
    CMyString(const char* str)
    {
        cout << "CMyString(const char* str)" << endl;
        if (str != nullptr)
        {
            mptr = new char[strlen(str) + 1];
            strcpy(mptr, str);
        }
        else
        {
            mptr = new char[1];
            *mptr = '\0';
        }
    }
    ~CMyString()
    {
        cout << "~CMyString()" << endl;
        delete[] mptr;
        mptr = nullptr;
    }
    CMyString(const CMyString& str)
    {
        cout << "CMyString(const CMyString& str)" << endl;
        mptr = new char[strlen(str.mptr) + 1];
        strcpy(mptr, str.mptr);
    }
    CMyString& operator=(const CMyString& str)
    {
        cout << "operator=" << endl;
        if (this == &str)
        {
            return *this;
        }
        delete[] mptr;
        mptr = new char[strlen(str.mptr) + 1];
        strcpy(mptr, str.mptr);
        return *this;
    }
    const char* c_str() const { return mptr; }
private:
    char* mptr;
};

CMyString GetMyString(CMyString &str)
{
    const char* temp = str.c_str();
    CMyString tempStr(temp);
    return tempStr;
}

int main()
{
    CMyString str("hello");
    CMyString str2;
    str2 = GetMyString(str);
    cout << str2.c_str() << endl;
    
    return 0;
}

在这里插入图片描述
当GetMyString函数中调用构造函数生成临时对象tempStr时,会开辟一块堆上内存复制 str 中 mptr 的内容,因为GetMyString函数结束,临时对象要析构,函数栈帧回退,故 tempStr 临时对象无法返回,此时返回的方式是通过调用拷贝构造,在main函数栈帧上开辟新的内存,创建对象来返回;然后main函数中 str2 调用赋值运算符重载复制GetMyString函数返回的内容(会开辟内存),参考上图,这样就会导致内存频繁开辟释放,如果对象复杂就会造成极大的资源浪费!!!

添加带右值引用参数的拷贝构造和赋值函数

int main()
{
	//左值:有内存、有名字	右值:没名字(临时量)、没内存
	int a = 10;		//定义一个左值
	int &b = a;		//左值引用
	//int &&c = a;	//无法将右值引用绑定到左值
	//int &c = 10;	//无法将左值引用绑定到右值
	/* 
	int tmp = 10;
	const int &c = tmp;
	*/
	const int &c = 10;
	/*
	int tmp = 20;
	const int &&d = tmp;
	*/
	int &&d = 20;	//右值引用
	CMyString &&e = CMyString("aaa");	//右值引用 临时对象
	
	//int &&f = d;	//无法用右值引用 引用一个右值引用变量
	int &f = d;		//一个右值引用变量,本身是一个左值

	return 0;
}

CString添加带右值引用参数的拷贝构造和赋值运算符重载函数

//带左值引用参数的拷贝构造
CMyString(const CMyString &str)
{
	cout << "CMyString(const CMyString&)" <<endl;
	mptr = new char[strlen(str.mptr) + 1];
    strcpy(mptr, str.mptr);
}
//带右值引用参数的拷贝构造
CMyString(CMyString &&str)	//str引用的是一个临时对象
{
	cout << "CMyString(CMyString&&)" <<endl;
	mptr = str.mptr;
	str.mptr = nullptr;
}
//带左值引用的赋值运算符重载函数
CMyString& operator=(const CMyString& str)
{
	cout << "operator=(const CMyString& str)" << endl;
    if (this == &str)
    {
        return *this;
    }
    delete[] mptr;
    mptr = new char[strlen(str.mptr) + 1];
    strcpy(mptr, str.mptr);
    return *this;
}
//带右值引用的赋值运算符重载函数
CMyString& operator=(CMyString&& str)	//接收临时对象
{
	cout << "operator=(CMyString&& str)" << endl;
    if (this == &str)
    {
        return *this;
    }
    delete[] mptr;
    
    mtpr = str.mptr;
    str.mptr = nullptr;
    return *this;
}

更改临时对象的mptr指针
在这里插入图片描述
operator+函数实现过程

CMyString operator+(const CMyString& str1, const CMyString& str2)
{
    char *ptmp = new char[strlen(str1.mptr) + strlen(str2.mptr) + 1];
    strcpy(ptmp, str1.mptr);
    strcat(ptmp, str2.mptr);
    CMyString tmpStr(ptmp);
    delete []ptmp;
    return tmpStr;	//CMyString& operator=(CMyString&& str)
}

int main()
{
    CMyString str1 = "hello";
    CMyString str2 = "world";
    CMyString str3 = str1 + str2;
    cout << str3 << endl;
    
    return 0;
}

这段代码实现了对于 “+” 的赋值运算符重载,首先开辟一块内存,之后调用构造函数,释放开辟的内存,最后返回对象;同样,这样的操作在赋值运算 “+” 的过程中,开辟新的内存用于临时存储指针内容,又马上释放,增加了开销,造成内存浪费,为了避免此问题,作出如下优化:

CMyString operator+(const CMyString& str1, const CMyString& str2)
{
    CMyString temp;
    temp.mptr = new char[strlen(str1.mptr) + strlen(str2.mptr) + 1];
    strcpy(temp.mptr, str1.mptr);
    strcat(temp.mptr, str2.mptr);
    return temp;		
}

int main()
{
    CMyString str1 = "hello";
    CMyString str2 = "world";
    CMyString str3 = str1 + str2;
    cout << str3 << endl;
    
    return 0;
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值