【C++】构造与析构性能分析

构造与析构的全部类型

定义数据类Data,复写所有构造和析构函数

class Data
{
public:
	int value;
	Data()
	{
		cout << "constructor: value = "<< value << " pointer: " << this << endl;
	}
	Data(int v) : value(v)
	{
		cout << "constructor: value = "<< value << " pointer: " << this << endl;
	}
	~Data()
	{
		cout << "destructor: value = "<< value << " pointer: " << this << endl;
	}
	Data(const Data& other)
	{
		value = other.value;
		cout << "copy1 constructor: value = "<< value << " pointer: " << this << endl;
	}
	Data& operator=(const Data& other)
	{
		value = other.value;
		cout << "copy2 constructor: value = "<< value << " pointer: " << this << endl;
		return *this;
	}
	Data(Data&& other)
	{
		value = other.value;
		cout << "move1 constructor: value = "<< value << " pointer: " << this << endl;
	}
	Data& operator=(Data&& other)
	{
		value = other.value;
		cout << "move2 constructor: value = "<< value << " pointer: " << this << endl;
		return *this;
	}
};

nrvo优化

函数返回临时创建的命名变量时,调用函数的地方可以省略拷贝或移动构造

Data getData(int v)
{
	Data tData1(v);
	return tData1;
}
int main() {
	cout << "d1" << endl;
	Data d1 = getData(1);
}

d1
constructor: value = 1 pointer: 000000E6B92FFC60
destructor: value = 1 pointer: 000000E6B92FFC60

整个过程只有一次构造函数,getData函数中的tData1直接构造在d1的地址上

无法应用nrvo的情况

当返回不止一条路径时,无法应用nrvo

Data getData(const Data& data, int v)
{
	Data tData1(v);
	if (v == 2)
	{
		Data tData2(v);
		tData2.value = 3;
		return tData2;
	}
	return tData1;
}

int main() {
	cout << "d1" << endl;
	Data d1 = getData(1);
	cout << "d2" << endl;
	Data d2 = getData(2);
}

d1
constructor: value = 1 pointer: 000000263B4FF7F0
move1 constructor: value = 1 pointer: 000000263B4FF870
destructor: value = 1 pointer: 000000263B4FF7F0
d2
constructor: value = 2 pointer: 000000263B4FF7F0
constructor: value = 2 pointer: 000000263B4FF850
destructor: value = 2 pointer: 000000263B4FF7F0
destructor: value = 3 pointer: 000000263B4FF850
destructor: value = 1 pointer: 000000263B4FF870

v = 1 时多了一次移动构造和一次析构,main里的d1是通过移动构造创建的
v = 2 时多了一次常规构造和一次析构,getData的tData2直接创建在了d2的地址,但是tData1的创建和析构被浪费了

Data getData(int v)
{
	Data tData1(v);
	Data tData2(3);
	return v == 2 ? tData2 : tData1;
}

d1
constructor: value = 1 pointer: 000000F2AA9EFE00
constructor: value = 3 pointer: 000000F2AA9EFDE0
copy1 constructor: value = 1 pointer: 000000F2AA9EFE80
destructor: value = 3 pointer: 000000F2AA9EFDE0
destructor: value = 1 pointer: 000000F2AA9EFE00
d2
constructor: value = 2 pointer: 000000F2AA9EFE00
constructor: value = 3 pointer: 000000F2AA9EFDE0
copy1 constructor: value = 3 pointer: 000000F2AA9EFE60
destructor: value = 3 pointer: 000000F2AA9EFDE0
destructor: value = 2 pointer: 000000F2AA9EFE00
destructor: value = 3 pointer: 000000F2AA9EFE60
destructor: value = 1 pointer: 000000F2AA9EFE80

这种写法性能更差,tData1和tData2都会被常规构造,d1和d2则被拷贝构造


Data getData(int v)
{
	return v == 2 ? Data(v) : Data(3);
}

d1
constructor: value = 3 pointer: 000000E52C8FF820
d2
constructor: value = 2 pointer: 000000E52C8FF800
destructor: value = 2 pointer: 000000E52C8FF800
destructor: value = 3 pointer: 000000E52C8FF820

这个时候应用了rvo优化

当数据从栈上转移到堆上时

大部分持久化的数据都是运行时创建的,一般会创建在堆上,此时如果要通过函数更新堆上数据的话,有以下几种写法:

class dynamicClass
{
public:
	Data data;
};
Data getData(int v)
{
	Data tData1(v);
	return tData1;
}
int main() {
	cout << "d1" << endl;
	dynamicClass* d1 = new dynamicClass();
	d1->data = getData(1);
	cout << "d2" << endl;
	dynamicClass* d2 = new dynamicClass();
	auto t2 = getData(2);
	d2->data = std::move(t2);
	cout << "d3" << endl;
	dynamicClass* d3 = new dynamicClass();
	d3->data = Data(3);
	cout << "delete" << endl;
	delete d1;
	delete d2;
	delete d3;
}

d1
constructor: value = 0 pointer: 000001B893F679E0
constructor: value = 1 pointer: 0000007B6F92F940
move2 constructor: value = 1 pointer: 000001B893F679E0
destructor: value = 1 pointer: 0000007B6F92F940
d2
constructor: value = 0 pointer: 000001B893F67C40
constructor: value = 2 pointer: 0000007B6F92F920
move2 constructor: value = 2 pointer: 000001B893F67C40
d3
constructor: value = 0 pointer: 000001B893F67C70
constructor: value = 3 pointer: 0000007B6F92F900
move2 constructor: value = 3 pointer: 000001B893F67C70
destructor: value = 3 pointer: 0000007B6F92F900
delete
destructor: value = 1 pointer: 000001B893F679E0
destructor: value = 2 pointer: 000001B893F67C40
destructor: value = 3 pointer: 000001B893F67C70
destructor: value = 2 pointer: 0000007B6F92F920

此时tData1无法直接创建在d1->data的地址上,因为tData1是栈地址,d1->data是堆地址,栈上的数据要转移到堆上必须经历一次额外的构造函数,如果有移动则优先移动构造,否则就进行拷贝构造
此时d2->data,d3->data的写法与d1->data都是等效的
因此对于堆上的地址,性能最优的方法是传引用或地址给函数

void fillData(Data& data, int v)
{
	data.value = v;
}

这种方式不会有任何额外的构造和析构开销

需要有默认值的变量返回

在getData外部构造默认值

Data getData(int v, const Data& defaultData)
{
	Data tData(v);
	if (v > 100)
	{
		return defaultData;
	}
	return tData;
}
void updateData(dynamicClass* d)
{
	Data defaultData(-1);
	cout << "test1" << endl;
	d->data = getData(1, defaultData);
	cout << "test2" << endl;
	d->data = getData(101, defaultData);
}
int main() {
	dynamicClass* d1 = new dynamicClass();
	cout << "start" << endl;
	updateData(d1);
	cout << "end" << endl;
	delete d1;
}

constructor: value = 0 pointer: 000001E7FFEB79E0
start
constructor: value = -1 pointer: 000000823112F6F0
test1
constructor: value = 1 pointer: 000000823112F650
move1 constructor: value = 1 pointer: 000000823112F6D0
destructor: value = 1 pointer: 000000823112F650
move2 constructor: value = 1 pointer: 000001E7FFEB79E0
destructor: value = 1 pointer: 000000823112F6D0
test2
constructor: value = 101 pointer: 000000823112F650
copy1 constructor: value = -1 pointer: 000000823112F6B0
destructor: value = 101 pointer: 000000823112F650
move2 constructor: value = -1 pointer: 000001E7FFEB79E0
destructor: value = -1 pointer: 000000823112F6B0
destructor: value = -1 pointer: 000000823112F6F0
end
destructor: value = -1 pointer: 000001E7FFEB79E0

由于返回不止一条路径,nrvo失效

Data getData(int v, const Data& defaultData)
{
	if (v > 100) return defaultData;
	
	Data tData(v);
	return tData;
}

constructor: value = 0 pointer: 0000022216F879E0
start
constructor: value = -1 pointer: 0000005CEA3DF960
test1
constructor: value = 1 pointer: 0000005CEA3DF940
move2 constructor: value = 1 pointer: 0000022216F879E0
destructor: value = 1 pointer: 0000005CEA3DF940
test2
copy1 constructor: value = -1 pointer: 0000005CEA3DF920
move2 constructor: value = -1 pointer: 0000022216F879E0
destructor: value = -1 pointer: 0000005CEA3DF920
destructor: value = -1 pointer: 0000005CEA3DF960
end
destructor: value = -1 pointer: 0000022216F879E0

将返回default提前后,不返回默认值的时候nrvo生效,返回默认值的时候则避免了无用的构造函数,情况好了很多。

还有类似的几种情况:

// nrvo失效
Data getData(int v, const Data& defaultData)
{
	Data tData(v);
	return v > 100 ? defaultData : tData;
}
// rvo生效
Data getData(int v, const Data& defaultData)
{
	return v > 100 ? defaultData : Data(v);
}

先将变量设为默认值

Data getData(int v, const Data& defaultData)
{
	if (v > 100)
	{
		return defaultData;
	}
	Data tData(v);
	return tData;
}
void updateData(dynamicClass* d)
{
	cout << "test1" << endl;
	d->data = Data(-1);
	d->data = getData(1, d->data);
	cout << "test2" << endl;
	d->data = Data(-1);
	d->data = getData(101, d->data);
}

constructor: value = 0 pointer: 000001EADF7779E0
start
test1
constructor: value = -1 pointer: 000000E92D8FF980
move2 constructor: value = -1 pointer: 000001EADF7779E0
destructor: value = -1 pointer: 000000E92D8FF980
constructor: value = 1 pointer: 000000E92D8FF960
move2 constructor: value = 1 pointer: 000001EADF7779E0
destructor: value = 1 pointer: 000000E92D8FF960
test2
constructor: value = -1 pointer: 000000E92D8FF940
move2 constructor: value = -1 pointer: 000001EADF7779E0
destructor: value = -1 pointer: 000000E92D8FF940
copy1 constructor: value = -1 pointer: 000000E92D8FF920
move2 constructor: value = -1 pointer: 000001EADF7779E0
destructor: value = -1 pointer: 000000E92D8FF920
end
destructor: value = -1 pointer: 000001EADF7779E0

因为d->data = Data(-1)这个操作也算从栈上转移到堆上,需要一次常规构造和一次移动构造。返回默认值的情况中即使默认值与d->data完全相同也会调用拷贝构造。总体来说这种写法性能极低

在函数getData内部构造默认值

Data getData(int v)
{
	if (v > 100)
	{
		return Data(-1);
	}
	Data tData(v);
	return tData;
}
void updateData(dynamicClass* d)
{
	cout << "test1" << endl;
	d->data = getData(1);
	cout << "test2" << endl;
	d->data = getData(101);
}

constructor: value = 0 pointer: 00000141F6C079E0
start
test1
constructor: value = 1 pointer: 00000028C8B6F8D0
move2 constructor: value = 1 pointer: 00000141F6C079E0
destructor: value = 1 pointer: 00000028C8B6F8D0
test2
constructor: value = -1 pointer: 00000028C8B6F8B0
move2 constructor: value = -1 pointer: 00000141F6C079E0
destructor: value = -1 pointer: 00000028C8B6F8B0
end
destructor: value = -1 pointer: 00000141F6C079E0

除了从栈上转移到堆上的移动构造外,基本上避免了所有额外开销

等效的写法有:

Data getData(int v)
{
	Data tData(v);
	if (v > 100)
	{
		tData.value = -1;
	}
	return tData;
}
Data getData(int v)
{
	return v > 100 ? Data(-1) : Data(v);
}

总结

提高临时变量构造效率的办法有
1.尽量保证只有一条返回路径
2.或者所有的返回值都是未命名变量
3.如果有多条路径,保证每条路径上只有一次构造,且构造出的变量为这条路径的返回值

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值