在 C++ STL标准库解析|简单实现STL容器string代码和迭代器我们写了一个自己的string,然而在写operator+时,这里返回了一个对象,且其中的代码效率较低(具体可以回看文章 C++ STL标准库解析|简单实现STL容器string代码和迭代器的结语部分)。
那么我有一些疑问现在算是搞懂了,现在分享出来。
String operator+(const String &lhs, const String &rhs) {
char *ptmp = new char[strlen(lhs._pstr) + strlen(rhs._pstr) + 1];
strcpy(ptmp, lhs._pstr);
strcat(ptmp, rhs._pstr);
String tmp(ptmp);
delete[] ptmp;
return tmp;
}
在这段代码中我们一共经历了ptmp的内存开辟,字符串复制和连接操作和内存释放;tmp的构造和析构。其中涉及的操作太多,导致性能较低。
临时对象的深拷贝
首先我们需要弄清楚,返回一个类对象会发生什么呢?
在 return tmp 时,如果不考虑返回值优化(RVO),编译器会自动为我们做一个 tmp 的深拷贝,然后返回 tmp 的深拷贝。函数返回后,在该作用域析构 tmp 本身。
也就是说,在执行return tmp
时,以下步骤会发生:
- 编译器会调用
tmp
的拷贝构造函数,创建一个新的String
对象,这个新对象是在调用函数的上下文中创建的。 - 然后函数返回这个新创建的对象。
- 原来的
tmp
对象在函数作用域结束时被析构。
返回值优化(ROV)
在现代 C++ 编译器中,返回值优化(RVO)是一种优化技术,可以在返回局部对象时避免不必要的深拷贝。在返回临时对象 tmp 时,编译器通常会直接构造返回值在调用者的上下文中,而不是在函数内部构造并复制到调用者上下文。这可以避免多余的复制和析构操作。
如果在没有 RVO 或者在一些情况下无法进行 RVO,C++11 引入了移动语义,可以通过移动构造函数来避免昂贵的深拷贝。当返回一个局部对象时,编译器可以选择使用移动构造函数,而不是复制构造函数。这意味着资源(如内存)可以从局部对象移动到新创建的对象,而不是复制它们。
移动语义
1. 移动构造函数
String(String&& other) noexcept : _pstr(other._pstr) {
other._pstr = nullptr;
}
将资源从 other 移动到新对象,并将 other 的指针设置为 nullptr。
2.移动赋值运算符
String& operator=(String&& other) noexcept {
if (this != &other) {
delete[] _pstr;
_pstr = other._pstr;
other._pstr = nullptr;
}
return *this;
}
释放当前对象的资源,将资源从 other 移动到当前对象,并将 other 的指针设置为 nullptr。
通过这种方式,当函数返回局部对象 tmp 时,编译器可以使用移动构造函数来高效地返回对象,而不是进行深拷贝。这确保了返回值在函数结束后仍然有效,同时避免了不必要的性能开销。
总结:
- 如果编译器应用了 RVO,临时对象 tmp 将直接在返回值的位置上构造,不会调用拷贝或移动构造函数。
- 如果编译器不能应用 RVO,它会调用移动构造函数来构造返回值,从而避免不必要的深拷贝。