一.问题引入
1. 如下代码会输出什么?
class String{
public:
explicit String(const char* p){
size_t size = std::strlen(p) + 1;
data = new char[size];
std::memcpy(data,p,size);
cout<<"1"<<endl;
}
~String(){
delete[] data;
}
String(const String& that){
size_t size = strlen(that.data) + 1;
data = new char[size];
std::memcpy(data,that.data,size);
cout<<"2"<<endl;
}
private:
char* data;
};
int main(){
auto temp = String(String("hi"));
return 0;
}
2.答案
3. 代码解读,问题提出
- 定义了两个
String 构造函数,一个使用const char* 进行初始化,另一个使用String 变量进行拷贝初始化 - 主函数里,我们用
const char* 的“hi” 初始化了一个临时String 变量, 再用这个临时String 变量初始化另一个String 变量 - 如果是按照上述思路执行的话,应该是输出
1和2和2 ,但是最后只输出了1 ,说明,没有执行临时量copy的过程,问题出在哪了?编译器优化?
二.借助汇编分析问题
1. 命令
g++ -o main.s -S main.cpp
2.内容如下
- const char* 构造函数
- main函数
3.汇编代码解读
4. 汇编层面解释调用成员函数
多说一句,类的成员函数如何被调用?
- 其实成员函数存储在
.text节 中, - 我们调用成员函数时,先在栈/堆上分配sizeof(类)的空间(设其地址为A),空类大小为1(而不是0),
- 此时这个空间的地址(地址A),就代表了
this 指针, - 我们将这个A(或称为
this 指针),传递到.text 节中的某个成员函数, - 成员函数隐式的第一个参数就为this,所以成员函数可以区分是哪个类在构造对象或调用对象,
- 成员函数随后在this(A地址)上进行各种操作,就是对A这块地址操作,就是对我们分配出的类实例在操作
- 不同的类实例有不同的this,即有不同的地址空间,所以我们可以使用一个成员函数操纵所有的类
5. 本函数调用的构造函数
可以看出,是const char*,而随后并没有再调用其他的构造函数了。 为什么没有调用拷贝构造函数? 原来是编译器帮我们做了优化!!!
三.问题解答:RVO优化
1. RVO优化是什么
-
返回值优化,return value optimization , 这是一种编译器优化机制,当函数返回一个对象的时候,如果自己创造一个临时对象进行返回(对应于main函数里,我们的String("hi") ),那么这个临时对象会消耗一个构造函数的调用(String(const char*) ),一个复制构造函数的调用(String(const String& s) ),以及一个析构函数的调用(析构掉临时值) -
经过返回值优化,就可以将成本降低到一个构造函数的代价。这样就省去了一次拷贝构造函数的调用和依次析构函数的调用。 -
注意从C++17开始,RVO优化不再是可选的,而是默认的
2. 关闭RVO优化
-fno-elide-constructors 选项可以取消编译器的 copy-elision 优化策略
3. NRVO优化是什么
- (Named Return Value Optimization)。具名返回值优化(NRVO),是对于按值返回“具名对象”(就是有名字的变量)时的优化手段,其实道理是一样的,但由于返回的值是具名变量,情况会复杂很多。所以,能执行优化的条件更苛刻。
4. 结合本例谈RVO优化底层如何实现
- 其实从上面我们对于汇编代码的讲解就已经提到了
- 我们直接从temp的地址进行构造,而不是先构造出一个临时变量,再把该变量的地址传给temp的拷贝构造函数,
- 编译器采取的是直接把
“hi” 的地址传给temp - 编译器足够智能!!!
四. 验证结果
1. 关掉RVO优化
- 输出了1,2,2
- 第一个2:临时变量传入拷贝构造函数,作为拷贝构造函数的参数
- 第二个2:构造完成,赋值给temp,调用拷贝构造函数
2. 查看底层汇编
- main函数的汇编语句
- 自己去尝试实现,读个两遍,再来看下面解释
- 可以看出,关闭优化后,拷贝构造函数也被加入了
.text 中,命名为_ZN6StringC1ERKS_ - 调用顺序也是符合预期
- 说明我们最开始的问题得到了验证。
- 如有错误,欢迎指正。
|