ToB企服应用市场:ToB评测及商务社交产业平台
标题:
类和对象——拷贝对象时的一些编译器优化
[打印本页]
作者:
熊熊出没
时间:
15 小时前
标题:
类和对象——拷贝对象时的一些编译器优化
拷贝对象时的一些编译器优化
在传参和传返回值的过程中,一般编译器会做一些优化(也就是说有的不做优化),减少对象的拷贝,这个在一些场景下照旧非常有效的。
这里只举几个案例,详细见书籍《深度探索c++对象模型》。
在20世纪末盛行的编译器(比方,vc++6.0)不会对这种情况举行优化。
案例1:仅使用类中的成员函数
很多时候,生成这个对象的目的仅仅是为了调用类中的某个函数。此时没须要生成一个对象,特殊是生成一个对象作为实参上传给平凡函数。
#include<iostream>
#include<cstdlib>
using namespace std;
class A {
public:
A(int a = 6)
:a(a) {
cout << "A(int a)" << endl;
}
A(const A& aa)
:a(aa.a) {
cout << "A(const A& aa)" << endl;
}
A& operator=(const A& aa) {
cout << "A& operator=(const A& aa)" << endl;
if (this != &aa) {
a = aa.a;
}
return *this;
}
~A() {
cout << "~A()" << endl;
}
void print() {
using std::cout;
cout << a << "\n";
}
private:
int a;
};
//若调用拷贝构造仅仅是为了调用这个函数,完全没必要传值传参
void f1_1(A a) {
a.print();
}
//所以直接加引用
void f1_2(A& a) {
a.print();
}
void f1() {
A a;
f1_1(a);
cout << endl;
f1_2(a);
cout << endl;
}
int main() {
f1();
return 0;
}
复制代码
案例2:案例1减少一次拷贝构造
首先,const对象不能调用非const成员函数。所以const对象也要准备对应的const函数重载。
其次,引用和const一般在一起,为了避免别名修改原来的对象(变量)。
最后,形参使用引用可以减少一次拷贝构造。
#include<iostream>
#include<cstdlib>
using namespace std;
class A {
public:
A(int a = 6)
:a(a) {
cout << "A(int a)" << endl;
}
A(const A& aa)
:a(aa.a) {
cout << "A(const A& aa)" << endl;
}
A& operator=(const A& aa) {
cout << "A& operator=(const A& aa)" << endl;
if (this != &aa) {
a = aa.a;
}
return *this;
}
~A() {
cout << "~A()" << endl;
}
//相应函数也要对这个类的成员函数进行限制防止权限放大
void print() const {
using std::cout;
cout << a << "\n";
}
private:
int a;
};
//为了支持生成临时对象,使用const引用
void f2_1(const A& a) {
a.print();
}
void f2_2(A& a) {
a.print();
}
void f2_3(A& a) {//非const形参,不具有常属性
a.print();
}
void f2() {
A a;
f2_1(a);//权限缩小
cout << endl;
f2_2(a);//权限平移
cout << endl;
//f2_3(A());//权限放大
f2_1(A());//形参也具有常属性时权限平移,可以调用
cout << endl;
}
int main() {
f2();
return 0;
}
复制代码
输出:
A(int a)
6
6
A(int a)
6
~A()
~A()
复制代码
f2_3(A());无法编译通过,因为临时对象、匿名对象都有常属性,上传无常属性形参的函数,权限放大。
案例3:临时对象也具有常属性
在案例2已经证明匿名对象具有常属性。隐式范例转换的临时对象也具有常属性。
#include<iostream>
#include<cstdlib>
using namespace std;
class A {
public:
A(int a = 6)
:a(a) {
cout << "A(int a)" << endl;
}
A(const A& aa)
:a(aa.a) {
cout << "A(const A& aa)" << endl;
}
A& operator=(const A& aa) {
cout << "A& operator=(const A& aa)" << endl;
if (this != &aa) {
a = aa.a;
}
return *this;
}
~A() {
cout << "~A()" << endl;
}
//相应函数也要对这个类的成员函数进行限制防止权限放大
void print() const {
using std::cout;
cout << a << "\n";
}
private:
int a;
};
//const引用能很好的支持生成临时对象
void f3_1(const A& a) {//这个地方引用和const一般同时出现防止不小心修改
a.print();
}
void f3() {//少调用一次拷贝构造
f3_1(A());//匿名对象有常属性
cout << endl;
f3_1(A(4));
cout << endl;
f3_1(3);//临时对象也具有常属性
cout << endl;
}
int main() {
f3();
return 0;
}
复制代码
输出:
A(int a)
6
~A()
A(int a)
4
~A()
A(int a)
3
~A()
复制代码
它们都被优化成了只调用一次构造函数。
案例4:const引用延伸生命周期
const引用可以延伸临时对象的生命周期,本质是将临时对象变成著名对象,如许临时对象就可以像著名对象一样生命周期在局部。
#include<iostream>
#include<cstdlib>
using namespace std;
class A {
public:
A(int a = 6)
:a(a) {
cout << "A(int a)" << endl;
}
A(const A& aa)
:a(aa.a) {
cout << "A(const A& aa)" << endl;
}
A& operator=(const A& aa) {
cout << "A& operator=(const A& aa)" << endl;
if (this != &aa) {
a = aa.a;
}
return *this;
}
~A() {
cout << "~A()" << endl;
}
//相应函数也要对这个类的成员函数进行限制防止权限放大
void print() const {
using std::cout;
cout << a << "\n";
}
private:
int a;
};
//缺省值为匿名对象
//const延长生命周期使得匿名对象存在于局部
void f4_1(const A& a = A()) {
a.print();
}
void f4() {
f4_1();
cout << endl;
//这里只有ref出了作用域,
//临时对象的生命周期才终止
const A& ref = A();
cout << endl;
ref.print();//还在{}也就是作用域内,可以使用
cout << endl;
}
int main() {
f4();
return 0;
}
复制代码
案例5:传匿名对象传参
编译器优化情况1:隐式范例转换作为实参,此时会调用两次构造。编译器将连续的两次构造(构造+拷贝构造)优化为直接构造。
c++尺度并没有对这种情况举行优化说明,这个实在照旧编译器自己的举动。在一些年代比较久远的编译器(比如20世纪末)就不会。
#include<iostream>
#include<cstdlib>
using namespace std;
class A {
public:
A(int a = 6)
:a(a) {
cout << "A(int a)" << endl;
}
A(const A& aa)
:a(aa.a) {
cout << "A(const A& aa)" << endl;
}
A& operator=(const A& aa) {
cout << "A& operator=(const A& aa)" << endl;
if (this != &aa) {
a = aa.a;
}
return *this;
}
~A() {
cout << "~A()" << endl;
}
//相应函数也要对这个类的成员函数进行限制防止权限放大
void print() const {
using std::cout;
cout << a << "\n";
}
private:
int a;
};
void f5_1(A a) {
a.print();
}//析构
void f5_2(const A a) {
a.print();
}
A f5_3() {
A a;
return a;
}
//隐式类型,连续构造(两次及以上)->优化为直接构造
void f5() {
//传值传参
//正常情况
A a;//构造
f5_1(a);//拷贝构造
cout << endl;
// 一个表达式中,构造+拷贝构造->优化为一个构造
f5_1(A());//匿名对象构造+拷贝构造被优化
cout << endl;
f5_1(A(3));
cout << endl;
f5_1(4);//隐式类型转换
cout << endl;
//这个也是构造+拷贝构造
A b = A(3);
cout << endl;
}
int main() {
f5();
return 0;
}
复制代码
输出:
A(int a)
A(const A& aa)
6
~A()
A(int a)
6
~A()
A(int a)
3
~A()
A(int a)
4
~A()
A(int a)
~A()
~A()
复制代码
分析:
f5_1(A());,f5_1(A(3));:匿名对象调用构造函数,加拷贝构造生成形参。
f5_1(4);:隐式转换,一次构造加拷贝构造。
A b = A(3);:一次构造加拷贝构造。
这三种情况,都被优化为一次构造。
案例6:函数传值返回时的优化
#include<iostream>
#include<cstdlib>
using namespace std;
class A {
public:
A(int a = 6)
:a(a) {
cout << "A(int a)" << endl;
}
A(const A& aa)
:a(aa.a) {
cout << "A(const A& aa)" << endl;
}
A& operator=(const A& aa) {
cout << "A& operator=(const A& aa)" << endl;
if (this != &aa) {
a = aa.a;
}
return *this;
}
~A() {
cout << "~A()" << endl;
}
//相应函数也要对这个类的成员函数进行限制防止权限放大
void print() const {
using std::cout;
cout << a << "\n";
}
private:
int a;
};
A f6_1() {
A a;//构造
return a;//拷贝构造生成临时对象
}
A& f6_2() {
A a;
return a;
}
void f6() {
A a;
cout << endl;
f6_1();
cout << endl;
a = f6_1();
cout << endl;
A ret = f6_1();
cout << endl;
A ret2 = f6_2();
cout << endl;
}
int main() {
f6();
return 0;
}
复制代码
输出:
A(int a)
A(int a)
A(const A& aa)
~A()
~A()
A(int a)
A(const A& aa)
~A()
A& operator=(const A& aa)
~A()
A(int a)
A(const A& aa)
~A()
A(int a)
~A()
A(const A& aa)
~A()
~A()
~A()
复制代码
单独看A ret = f6_1();这种情况:
A f6_1()在return语句会生成临时对象,但编译器举行了优化,直接将这个a在生命周期结束前拷贝给ret。
所以在一个表达式的连续两个步调里,局部对象构造 + 传值返回生成临时对象调用拷贝构造,两次调用构造被优化为一次。
而A ret2 = f6_2();因为f6_2是传引用返回,所以直接省去了return语句的一次拷贝构造,在析构前生成临时对象,之后通过拷贝构造将对象拷贝给ret2。
案例7:优化的条件
#include<iostream>
#include<cstdlib>
using namespace std;
class A {
public:
A(int a = 6)
:a(a) {
cout << "A(int a)" << endl;
}
A(const A& aa)
:a(aa.a) {
cout << "A(const A& aa)" << endl;
}
A& operator=(const A& aa) {
cout << "A& operator=(const A& aa)" << endl;
if (this != &aa) {
a = aa.a;
}
return *this;
}
~A() {
cout << "~A()" << endl;
}
//相应函数也要对这个类的成员函数进行限制防止权限放大
void print() const {
using std::cout;
cout << a << "\n";
}
private:
int a;
};
A f7_1() {
A a;
return a;
}
void f7() {//这种情况编译器不会再优化
A ret2;
ret2 = f7_1();
}
int main() {
f7();
return 0;
}
复制代码
f7()这种情况不能优化,两个缘故原由:
同范例才气优化(都是构造或都是拷贝构造才气优化,这里是构造和赋值)。
不在同一步调(声明对象和赋值重载是两个语句或者说步调)。
案例8:隐式范例转换的优化
和案例6的情况相似,都是构造临时对象并返回,只是存在隐式范例转换。所以被优化为一次构造。
#include<iostream>
#include<cstdlib>
using namespace std;
class A {
public:
A(int a = 6)
:a(a) {
cout << "A(int a)" << endl;
}
A(const A& aa)
:a(aa.a) {
cout << "A(const A& aa)" << endl;
}
A& operator=(const A& aa) {
cout << "A& operator=(const A& aa)" << endl;
if (this != &aa) {
a = aa.a;
}
return *this;
}
~A() {
cout << "~A()" << endl;
}
//相应函数也要对这个类的成员函数进行限制防止权限放大
void print() const {
using std::cout;
cout << a << "\n";
}
private:
int a;
};
//被优化为直接构造
//构造匿名对象加临时对象,两次构造被优化为1次
A f8_1() {
return A();
}
A f8_2() {
return 8;
}
A f8_3() {
return A(1);
}
void f8() {
A a1 = f8_1();
cout << endl;
A a2 = f8_2();//隐式类型转换
cout << endl;
A a3 = f8_3();
cout << endl;
}
int main() {
f8();
return 0;
}
复制代码
所以就有了如许一个特性:局部对象都只能传值返回,因此可以的话尽大概使用临时对象返回或隐式范例转换,可以减少拷贝调用次数。
再次理解封装
现实生活中的实体计算机并不认识,计算机只认识二进制格式的数据。如果想要让计算机认识现实生活中的实体,用户必须通过某种面向对象的语言,对实体举行描述,然后通过编写步伐,创建对象后计算机才可以认识。
比如想要让计算机认识洗衣机,就需要:
用户先要对现实中洗衣机实体举行抽象——即在人为头脑层面对洗衣机举行认识,洗衣机有什么属性,有那些功能,即对洗衣机举行抽象认知的一个过程。
经过1之后,在人的头脑中已经对洗衣机有了一个苏醒的认识,只不外此时计算机还不清楚,想要让计算机识别人想象中的洗衣机,就需要人通过某种面向对象的语言(比如:c++、java、python等)将洗衣机用类来举行描述,并输入到计算机中。
经过2之后,在计算机中就有了一个洗衣机类,但是洗衣机类只是站在计算机的角度对洗衣机对象举行描述的,通过洗衣机类,可以实例化出一个个详细的洗衣机对象,此时计算机才气洗衣机是什么东西。
用户就可以借助计算机中洗衣机对象,来模拟现实中的洗衣机实体了。
所以
类是对某一类实体
(
对象
)
来举行描述的
,描述该对象具有那些
属性
,那些
方法
,描述完成后就
形成
了一种新的
自定义范例
,才用该自定义范例就可以实例化详细的对象。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/)
Powered by Discuz! X3.4