对象被优化后, 才是高效的c++编程
文章目录
- 对象被优化后, 才是高效的c++编程
- 1.对象使用背后调用了哪些方法
- 2.函数调用过程中对象背后调用方法
- 3.总结三条对象优化的规则
- 4.CMyString的代码问题
- 5.添加带右值引用参数的拷贝构造和赋值函数
- 6.String类在vector上的应用--面试题
- 7.move移动语义和forword类型完美转发
- move移动语义的作用
- 代码:
- **问题: **
- 解决办法:
- 最终代码:
- forward完美转发内容
- 引用折叠
- push_back模板 -- 直接替代左右值引用push_back
- forward完美转发
- 带有完美转发的 完整代码
- 总结
注意, 这节讲的, 有很多 和现代编译器的结果不同, 注意RVO的默认开启
1.对象使用背后调用了哪些方法
-
代码实例:
#include <iostream> #include <vector> #include <algorithm> #include <functional> using namespace std; class Test{ public:Test(int a = 10) :ma(a){cout << "Test()" << endl;}~Test(){cout << "~Test()" << endl;}Test(const Test& t) :ma(t.ma){cout << "Test(const Test&)" << 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; //拷贝构造//c++编译器对于对象构造的优化: 用临时对象生成新对象时,临时对象就不产生了, 直接构造新对象就行了Test t4 = Test(20); // Test(20)临时对象,生存周期: 所在语句 //等价于Test t5(20);cout << "------------" << endl;t4 = t2; // 赋值, 不是构造//operator=, 需要传参, //这两是 显示生成临时对象t4 = Test(30); // 赋值, 这个临时对象必须生成, t4 = (Test)20; //int->Test(int), 编译器发现要转的类型里有int , 就可以编译器隐式转换, // 隐式生成临时对象t4 = 30; //int->Test(int)cout << "------------" << endl;Test* p = &Test(50); // 临时对象, 出了这个语句就没了, 析构了. 地址也就没了const Test& ref = Test(50); // 引用临时对象, 需要常引用, vs版本高, 检查严格// 引用是可以的, 引用虽然和指针很像, 但实际是 起别名, 离开这个语句, 临时对象不会析构//引用在C++中是一个别名(alias),它本质上是对某个对象的另一个名字。//临时对象变为了 引用变量, 声明周期是整个函数里return 0; }
-
结论:指针指向临时变量是不安全的, 但是 引用 是安全的
-
代码示例: 各种情况
#include <iostream> #include <vector> #include <algorithm> #include <functional> 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& t) : ma(t.ma), mb(t.mb) {cout << "Test(const Test&)" << endl;}Test& operator=(const Test& t) {cout << "operator=" << endl;ma = t.ma;mb = t.mb;return *this;} private:int ma;int mb; };Test t1(10, 10); // 1. 全局变量先构造, 普通构造int main() {Test t2(20, 20); // 3. 普通构造Test t3 = t2; // 4. 拷贝构造static Test t4 = Test(30, 30); // 5. 静态局部变量, 程序运行到这里才 构造 临时对象被优化, 仅t4的普通构造 等价于 static Test t4(30, 30)t2 = Test(40, 40); // 6. 显示生成临时对象 先临时对象普通构造, 再operator= 析构t2 = (Test)(50, 50); // 7. 隐式生成临时对象 先临时对象普通构造, 再operator= 析构t2 = 60; //8. 隐式生成临时对象 先临时对象普通构造, 再operator= 析构Test* p1 = new Test(70, 70); // 9.指针, new了, 非临时对象, 普通构造 , 不析构, 没有deleteTest* p2 = new Test[2]; // 10. 数组, 两次普通构造 , 不析构, 没有delete//Test* p3 = &Test(80, 80); // 11. 指针, 普通构造 , 但是出了这句 就析构了 成为 悬空指针 会被警告, 无法编译执行const Test& p4 = Test(90, 90); //12. 引用, 普通构造, 出了这句不析构, 出了函数析构delete p1; // 13. 析构 p1delete[] p2; // 14. 析构 p2,两次析构 } // 15. 12析构, 4析构, 3析构, 5析构, 静态局部变量在数据段, 程序结束才析构, 2析构, 1析构Test t5(100, 100); // 2. 全局变量先构造, 普通构造
2.函数调用过程中对象背后调用方法
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;class Test {
public:Test(int a = 10) :ma(a) {cout << "Test()" << endl;}~Test() {cout << "~Test()" << endl;}Test(const Test& t) :ma(t.ma) {cout << "Test(const Test&)" << endl;}Test& operator=(const Test& t) {cout << "operator=" << endl;ma = t.ma;return *this;}int GetData()const {return ma;}
private:int ma;
};Test GetObject(Test t) {int val = t.GetData();Test tmp(val);return tmp; //不能返回 局部的或者临时对象的指针/*static Test tmp(val);return &tmp;*/
}int main() {Test t1; // 1. 调用默认构造函数Test t2; // 2. 调用默认构造函数t2 = GetObject(t1); // 3. 调用拷贝构造函数(参数传递)// 4. 调用默认构造函数(创建 tmp)// 5. 调用拷贝构造函数(返回 tmp) 会返回一个临时对象, 这个不会被优化哈// 6. 析构 tmp// 7. 析构参数 t// 8. 调用赋值运算符(将返回值赋给 t2)// 9. 析构临时对象return 0;// 10. 析构 t2// 11. 析构 t1
}Test()
Test()
Test(const Test&)
Test()
~Test()
operator=
~Test()
~Test()
~Test()
注意: 以上标注只是NRVO情况, 现在的编译器默认开启RVO----自己补充 会变为9个
//5和9 由于现在大多数默认启用了RVO, 将不会存在 RVO 在 C++17 之后变成“强制优化”
// RVO(Return Value Optimization,返回值优化)是 C++ 编译器的一种优化技术,用于减少临时对象的创建,提升程序性能。它的核心思想是 避免拷贝构造,直接在目标位置构造对象。// RVO优化后
/*
Test GetObject(Test t) {int val = t.GetData();return Test(val); // 直接构造在 t2 的内存位置上
}*/
3.总结三条对象优化的规则
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;class Test {
public:Test(int a = 10) :ma(a) {cout << "Test()" << endl;}~Test() {cout << "~Test()" << endl;}Test(const Test& t) :ma(t.ma) {cout << "Test(const Test&)" << endl;}Test& operator=(const Test& t) {cout << "operator=" << endl;ma = t.ma;return *this;}int GetData()const {return ma;}
private:int ma;
};Test GetObject(Test& t) { // 仅换为引用, 就减少了 t的拷贝构造和析构 int val = t.GetData();// Test tmp(val);//return tmp; return Test(val); // 返回临时对象, 会直接在main函数 构造临时对象 //这个在现代编译器无变化, RVO会默认开启//临时对象 优化 比较复杂, 先浅理解}int main() {Test t1; Test t2 = GetObject(t1); //而如果用临时对象 拷贝构造 新对象, 那么临时对象就不产生了, 直接构造新对象 减少为 4个, 直接调用构造, 也不需要 operator=return 0;}
-
函数参数传递过程中, 对象 优先 按 引用传递, 不要 值传递, 会减少很多 构造析构
//未开启RVO Test() Test() Test(const Test&) // t拷贝构造 Test() Test(const Test&) ~Test() ~Test() // t析构 operator= ~Test() ~Test() ~Test() //变为Test() Test() Test() Test(const Test&) ~Test() operator= ~Test() ~Test() ~Test()
-
当函数 返回对象的 时候, 应该返回一个 临时对象, 而不要返回 一个定义过得 对象—这个在现代编译器无变化, RVO会默认开启
Test() Test() Test() Test(const Test&) // 返回值的tmp 拷贝构造 给main里的 临时对象 ~Test() // 析构返回值的这个 tmp operator= ~Test() ~Test() ~Test() //变为Test() Test() Test() // 返回临时对象, 会直接在main函数 构造临时对象 operator= ~Test() ~Test() ~Test()
-
当函数返回一个对象时,优先使用初始化方式接收返回值,而不是赋值方式。
使用临时对象 直接 初始化 对象, 将会舍弃临时对象, 直接 使用默认构造Test() Test() Test() // 返回临时对象, 会直接在main函数 构造临时对象 operator= // 临时对象 operator= 赋值 ~Test() // 析构临时对象 ~Test() ~Test()//变为 Test() Test() // 直接默认构造 t2, 不要临时对象了 ~Test() ~Test()
4.CMyString的代码问题
String类中间 会有大量的 new和 delete, 如果不做 对象优化, 效率会非常低!!!
5.添加带右值引用参数的拷贝构造和赋值函数
重点最后两个函数
#include <iostream>
#include <cstring>
using namespace std;
class String
{
public:// 构造函数String(const char* p = nullptr){if (p != nullptr){_pstr = new char[strlen(p) + 1];cout << "new" << endl;strcpy(_pstr, p);}else{_pstr = new char[1];cout << "new" << endl;*_pstr = '\0';}cout << "String()" << endl;}// 析构函数~String(){delete[] _pstr;cout << "delete" << endl;_pstr = nullptr;cout << "~String()" << endl;}// 拷贝构造函数String(const String& other){_pstr = new char[strlen(other._pstr) + 1];cout << "new" << endl;strcpy(_pstr, other._pstr);cout << "String(const String)" << endl;}// 赋值运算符重载String& operator=(const String& other){if (this != &other) // 防止自赋值{delete[] _pstr;cout << "delete" << endl;_pstr = new char[strlen(other._pstr) + 1];cout << "new" << endl;strcpy(_pstr, other._pstr);}cout << "operator=" << endl;return *this;}加法运算符重载--未优化, 多了一次new,delete//String operator+(const String& other) const//{// char* newtmp = new char[strlen(_pstr) + strlen(other._pstr) + 1];// strcpy(newtmp, _pstr);// strcat(newtmp, other._pstr);// String newString(newtmp);// delete[]newtmp;// return newString;//}// 加法运算符重载--小优化后String operator+(const String& other) const{String newString;newString._pstr = new char[strlen(_pstr) + strlen(other._pstr) + 1];cout << "new" << endl;strcpy(newString._pstr, _pstr);strcat(newString._pstr, other._pstr);return newString;}// 比较运算符重载bool operator>(const String& other) const{return strcmp(_pstr, other._pstr) > 0;}bool operator<(const String& other) const{return strcmp(_pstr, other._pstr) < 0;}bool operator==(const String& other) const{return strcmp(_pstr, other._pstr) == 0;}// 长度方法size_t length() const{return strlen(_pstr);}// 下标运算符重载char& operator[](size_t index){return _pstr[index];}const char& operator[](size_t index) const{return _pstr[index];}// 输出运算符重载friend std::ostream& operator<<(std::ostream& os, const String& str){os << str._pstr;return os;}// 输入运算符重载friend std::istream& operator>>(std::istream& is, String& str){char buffer[1024];is >> buffer;str = String(buffer);return is; // 支持链式操作 例如: cin>>a>>b,第一次cin>>a返回cin, 即后面的变为 cin>>b}const char* c_str()const { return _pstr; }private:char* _pstr;
};String GetString(String& str)
{const char* pstr = str.c_str();String tmpstr(pstr);return tmpstr; // 不开启RVO时, main函数这里会拷贝构造临时对象//return String(pstr);
}int main()
{String str1 ="aaaaaaaaaa";String str2;str2 = GetString(str1);cout << str2.c_str() << endl;return 0;
}
-
在String类中, 开启RVO, 并不使用operator= 的构造,new,delete,析构 顺序
new String() new String() new String() delete new operator= delete ~String() aaaaaaaaaa delete ~String() delete ~String()
-
回顾右值引用: ---- 右值是 没有名字(临时量)或者没内存
普通引用只能引用左值, 且
常引用 可以 引用 右值, 但不能修改右值引用 引用右值, 且能改变值
int a = 10;int& b = a;cout << b << endl;//int&& c = a; // 这是不行的int&& c = 20; // c可以改变临时量值c = 40;int &f = c;cout << c << endl;int tmp = 20;int& d = tmp; d = 30;cout << d << endl;const int& e = 20; // 常引用可以 引用右值, 但不能修改 值//e = 10;cout << e << endl;
-
带右值引用参数的拷贝构造-------- 实际代码里 没用–RVO使得没有 拷贝构造了
String(String&& other) // 换成右值引用 {//临时对象进入_pstr = other._pstr; // 直接指向同一个资源other._pstr = nullptr;cout << "String(String&&)" << endl;}
-
带右值引用参数的operator= -------- 这个在代码里是 实际有效的 — 减少一次 new
String& operator=(String&& other){if (this != &other) // 防止自赋值{delete[] _pstr;cout << "delete" << endl;_pstr = other._pstr; // 直接指向同一个资源other._pstr = nullptr;}return *this;}
-
总体代码:
#include <iostream> #include <cstring> using namespace std; class String { public:// 构造函数String(const char* p = nullptr){if (p != nullptr){_pstr = new char[strlen(p) + 1];cout << "new" << endl;strcpy(_pstr, p);}else{_pstr = new char[1];cout << "new" << endl;*_pstr = '\0';}cout << "String()" << endl;}// 析构函数~String(){delete[] _pstr;cout << "delete" << endl;_pstr = nullptr;cout << "~String()" << endl;}// 左值引用拷贝构造函数String(const String& other){_pstr = new char[strlen(other._pstr) + 1];cout << "new" << endl;strcpy(_pstr, other._pstr);cout << "String(const String)" << endl;}//右值引用拷贝构造String(String&& other) // 换成右值引用 {//临时对象进入_pstr = other._pstr; // 直接指向同一个资源other._pstr = nullptr;cout << "String(String&&)" << endl;}// 左值引用赋值运算符重载String& operator=(const String& other){if (this != &other) // 防止自赋值{delete[] _pstr;cout << "delete" << endl;_pstr = new char[strlen(other._pstr) + 1];cout << "new" << endl;strcpy(_pstr, other._pstr);}cout << "operator=" << endl;return *this;}//右值引用赋值运算符重载String& operator=(String&& other){if (this != &other) // 防止自赋值{delete[] _pstr;cout << "delete" << endl;_pstr = other._pstr; // 直接指向同一个资源other._pstr = nullptr;}cout << "operator=&&" << endl;return *this;}加法运算符重载--未优化, 多了一次new,delete//String operator+(const String& other) const//{// char* newtmp = new char[strlen(_pstr) + strlen(other._pstr) + 1];// strcpy(newtmp, _pstr);// strcat(newtmp, other._pstr);// String newString(newtmp);// delete[]newtmp;// return newString;//}// 加法运算符重载--小优化后String operator+(const String& other) const{String newString;newString._pstr = new char[strlen(_pstr) + strlen(other._pstr) + 1];cout << "new" << endl;strcpy(newString._pstr, _pstr);strcat(newString._pstr, other._pstr);return newString;}// 比较运算符重载bool operator>(const String& other) const{return strcmp(_pstr, other._pstr) > 0;}bool operator<(const String& other) const{return strcmp(_pstr, other._pstr) < 0;}bool operator==(const String& other) const{return strcmp(_pstr, other._pstr) == 0;}// 长度方法size_t length() const{return strlen(_pstr);}// 下标运算符重载char& operator[](size_t index){return _pstr[index];}const char& operator[](size_t index) const{return _pstr[index];}// 输出运算符重载friend std::ostream& operator<<(std::ostream& os, const String& str){os << str._pstr;return os;}// 输入运算符重载friend std::istream& operator>>(std::istream& is, String& str){char buffer[1024];is >> buffer;str = String(buffer);return is; // 支持链式操作 例如: cin>>a>>b,第一次cin>>a返回cin, 即后面的变为 cin>>b}const char* c_str()const { return _pstr; }private:char* _pstr; };String GetString(String& str) {const char* pstr = str.c_str();String tmpstr(pstr);return tmpstr; // 不开启RVO时, main函数这里会拷贝构造临时对象//return String(pstr); }int main() {String str1 ("aaaaaaaaaa");String str2;str2 = GetString(str1);cout << str2.c_str() << endl;return 0; }
new String() new String() new String() delete operator=&& delete ~String() aaaaaaaaaa delete ~String() delete ~String()
6.String类在vector上的应用–面试题
#include <iostream>
#include <cstring>
#include <vector>
using namespace std;
#include <iostream>
#include <cstring>
using namespace std;
class String
{
public:// 构造函数String(const char* p = nullptr){if (p != nullptr){_pstr = new char[strlen(p) + 1];cout << "new" << endl;strcpy(_pstr, p);}else{_pstr = new char[1];cout << "new" << endl;*_pstr = '\0';}cout << "String()" << endl;}// 析构函数~String(){delete[] _pstr;cout << "delete" << endl;_pstr = nullptr;cout << "~String()" << endl;}// 左值引用拷贝构造函数String(const String& other){_pstr = new char[strlen(other._pstr) + 1];cout << "new" << endl;strcpy(_pstr, other._pstr);cout << "String(const String)" << endl;}//右值引用拷贝构造String(String&& other) // 换成右值引用 {//临时对象进入_pstr = other._pstr; // 直接指向同一个资源other._pstr = nullptr;cout << "String(String&&)" << endl;}// 左值引用赋值运算符重载String& operator=(const String& other){if (this != &other) // 防止自赋值{delete[] _pstr;cout << "delete" << endl;_pstr = new char[strlen(other._pstr) + 1];cout << "new" << endl;strcpy(_pstr, other._pstr);}cout << "operator=" << endl;return *this;}//右值引用赋值运算符重载String& operator=(String&& other){if (this != &other) // 防止自赋值{delete[] _pstr;cout << "delete" << endl;_pstr = other._pstr; // 直接指向同一个资源other._pstr = nullptr;}cout << "operator=&&" << endl;return *this;}加法运算符重载--未优化, 多了一次new,delete//String operator+(const String& other) const//{// char* newtmp = new char[strlen(_pstr) + strlen(other._pstr) + 1];// strcpy(newtmp, _pstr);// strcat(newtmp, other._pstr);// String newString(newtmp);// delete[]newtmp;// return newString;//}// 加法运算符重载--小优化后String operator+(const String& other) const{String newString;newString._pstr = new char[strlen(_pstr) + strlen(other._pstr) + 1];cout << "new" << endl;strcpy(newString._pstr, _pstr);strcat(newString._pstr, other._pstr);return newString;}// 比较运算符重载bool operator>(const String& other) const{return strcmp(_pstr, other._pstr) > 0;}bool operator<(const String& other) const{return strcmp(_pstr, other._pstr) < 0;}bool operator==(const String& other) const{return strcmp(_pstr, other._pstr) == 0;}// 长度方法size_t length() const{return strlen(_pstr);}// 下标运算符重载char& operator[](size_t index){return _pstr[index];}const char& operator[](size_t index) const{return _pstr[index];}// 输出运算符重载friend std::ostream& operator<<(std::ostream& os, const String& str){os << str._pstr;return os;}// 输入运算符重载friend std::istream& operator>>(std::istream& is, String& str){char buffer[1024];is >> buffer;str = String(buffer);return is; // 支持链式操作 例如: cin>>a>>b,第一次cin>>a返回cin, 即后面的变为 cin>>b}const char* c_str()const { return _pstr; }private:char* _pstr;
};String GetString(String& str)
{const char* pstr = str.c_str();String tmpstr(pstr);return tmpstr; // 不开启RVO时, main函数这里会拷贝构造临时对象//return String(pstr);
}int main()
{String str1 = "aaaaaaaaaa";vector<String> vec;vec.reserve(10);vec.push_back(str1); //vec.push_back(String("bbbb"));return 0;
}
面试题: 问 两次push_back 分别是 使用了什么拷贝构造?!!!
一次左值引用拷贝, 一次右值引用拷贝
右值引用相比 左值引用, 更高效
new
String()
new
String(const String)
new
String()
String(String&&)
delete
~String()
delete
~String()
delete
~String()
delete
~String()
7.move移动语义和forword类型完美转发
move移动语义的作用
代码:
上一节代码里, 加上 很早之前写的 Vector类: 添加一个push_back的右值引用
#include <iostream>
#include <cstring>
using namespace std;#include <iostream>
using namespace std;template<typename T>
struct Allocator
{T* allocate(size_t size)// 开辟内存{return (T*)malloc(sizeof(T) * size);}void deallocate(void* p) // 释放内存{free(p);}void construct(T* p, const T& val) //对象构造{new (p) T(val); //定位new/*作用是在一块已经分配好的内存上构造一个对象,而不是通过 new 运算符动态分配内存。p 是一个指针,指向一块预先分配好的内存。T(val) 表示调用类型 T 的构造函数,并传递参数 val。new (p) T(val) 的意思是在 p 指向的内存地址上构造一个 T 类型的对象,并调用构造函数 T(val)。*/}void destroy(T* p) // 对象析构{p->~T(); //~T()代表T类型的析构函数}
};template<typename T, typename Alloc = Allocator<T>> //Alloc默认是Allocator<T>
class Vector
{
private:T* _first;//数组起始,与数组名T* _last;//数组最后位置的下一个T* _end;//空间的后面位置Alloc _allocator;//定义容器空间配置对象void expand() //二倍扩容{int size = _last - _first;//T* ptmp = new T[2 * size];T* ptmp = _allocator.allocate(2 * size);for (int i = 0; i < size; i++){//ptmp[i] = _first[i];_allocator.construct(ptmp + i, _first[i]);}//delete[]_first;for (T* p = _first; p != _last; ++p){_allocator.destroy(p); //析构_first指针指向的数组的有效元素}_first = ptmp;_last = _first + size;_end = _first + 2 * size;}public:Vector(int size = 10){//_first = new T[size];// 只开辟内存_first = _allocator.allocate(size);_last = _first;_end = _first + size;}~Vector(){//delete[]_first;// 析构有效的元素并释放内存for (T* p = _first; p != _last; ++p){_allocator.destroy(p); //析构_first指针指向的数组的有效元素}_allocator.deallocate(_first); //释放堆上的数组内存_first = _last = _end = nullptr;}Vector(const Vector<T>& src){int size = src._end - src._first;//_first = new T[size];_first = _allocator.allocate(size);int len = src._last - src._first;for (int i = 0; i < len; i++){//_first[i] = src._first[i];_allocator.construct(_first + i, src._first[i]);}_last = _first + len;_end = _first + size;}Vector<T>& operator=(const Vector<T>& src){if (this == &src){return *this;}//delete[]_first;for (T* p = _first; p != _last; ++p){_allocator.destroy(p); //析构_first指针指向的数组的有效元素}int size = src._end - src._first;//_first = new T[size];_first = _allocator.allocate(size);int len = src._last - src._first;for (int i = 0; i < len; i++){//_first[i] = src._first[i];_allocator.construct(_first + i, src._first[i]);}_last = _first + len;_end = _first + size;return *this;}//添加右值引用push_backvoid push_back(T&& val) //向容器末尾添加元素{if (full()){expand();}_allocator.construct(_last, val);_last++;}void push_back(const T& val) //向容器末尾添加元素{if (full()){expand();}//*_last++ = val;_allocator.construct(_last, val);_last++;}void pop_back() //向容器末尾删除元素{if (empty()){return;}--_last;_allocator.destroy(_last);}bool full()const{return _last == _end;}bool empty()const{return _last == _first;}T back()const // 返回末尾元素{return *(_last - 1);// *(--_last)错误的, 本函数const方法, 不能修改成员变量, _last-1是偏移量, --会改变last值}
};class String
{
public:// 构造函数String(const char* p = nullptr){if (p != nullptr){_pstr = new char[strlen(p) + 1];cout << "new" << endl;strcpy(_pstr, p);}else{_pstr = new char[1];cout << "new" << endl;*_pstr = '\0';}cout << "String()" << endl;}// 析构函数~String(){delete[] _pstr;cout << "delete" << endl;_pstr = nullptr;cout << "~String()" << endl;}// 左值引用拷贝构造函数String(const String& other){_pstr = new char[strlen(other._pstr) + 1];cout << "new" << endl;strcpy(_pstr, other._pstr);cout << "String(const String)" << endl;}//右值引用拷贝构造String(String&& other) // 换成右值引用 {//临时对象进入_pstr = other._pstr; // 直接指向同一个资源other._pstr = nullptr;cout << "String(String&&)" << endl;}// 左值引用赋值运算符重载String& operator=(const String& other){if (this != &other) // 防止自赋值{delete[] _pstr;cout << "delete" << endl;_pstr = new char[strlen(other._pstr) + 1];cout << "new" << endl;strcpy(_pstr, other._pstr);}cout << "operator=" << endl;return *this;}//右值引用赋值运算符重载String& operator=(String&& other){if (this != &other) // 防止自赋值{delete[] _pstr;cout << "delete" << endl;_pstr = other._pstr; // 直接指向同一个资源other._pstr = nullptr;}cout << "operator=&&" << endl;return *this;}加法运算符重载--未优化, 多了一次new,delete//String operator+(const String& other) const//{// char* newtmp = new char[strlen(_pstr) + strlen(other._pstr) + 1];// strcpy(newtmp, _pstr);// strcat(newtmp, other._pstr);// String newString(newtmp);// delete[]newtmp;// return newString;//}// 加法运算符重载--小优化后String operator+(const String& other) const{String newString;newString._pstr = new char[strlen(_pstr) + strlen(other._pstr) + 1];cout << "new" << endl;strcpy(newString._pstr, _pstr);strcat(newString._pstr, other._pstr);return newString;}// 比较运算符重载bool operator>(const String& other) const{return strcmp(_pstr, other._pstr) > 0;}bool operator<(const String& other) const{return strcmp(_pstr, other._pstr) < 0;}bool operator==(const String& other) const{return strcmp(_pstr, other._pstr) == 0;}// 长度方法size_t length() const{return strlen(_pstr);}// 下标运算符重载char& operator[](size_t index){return _pstr[index];}const char& operator[](size_t index) const{return _pstr[index];}// 输出运算符重载friend std::ostream& operator<<(std::ostream& os, const String& str){os << str._pstr;return os;}// 输入运算符重载friend std::istream& operator>>(std::istream& is, String& str){char buffer[1024];is >> buffer;str = String(buffer);return is; // 支持链式操作 例如: cin>>a>>b,第一次cin>>a返回cin, 即后面的变为 cin>>b}const char* c_str()const { return _pstr; }private:char* _pstr;
};String GetString(String& str)
{const char* pstr = str.c_str();String tmpstr(pstr);return tmpstr; // 不开启RVO时, main函数这里会拷贝构造临时对象//return String(pstr);
}int main()
{String str1 = "aaaaaaaaaa";Vector<String> vec;vec.push_back(str1); //vec.push_back(String("bbbb"));return 0;
}
new
String()
new
String(const String)
new
String()
new
String(const String)
delete
~String()
delete
~String()
delete
~String()
delete
~String()
**问题: **
发现没有使用 到 右值引用
//添加右值引用push_backvoid push_back(const T&& val) //向容器末尾添加元素{if (full()){expand();}_allocator.construct(_last, val);_last++;}
是因为, 这里面 给 _allocator.construct()的val 还是左值引用
解决办法:
-
move移动语义, 强转成 右值引用类型
_allocator.construct(_last, val); //变为 _allocator.construct(_last, move(val)); //std::move
-
添加 construct 右值引用
//右值引用 construct 里面的 也需要 move val void construct(T* p, T&& val) //对象构造 {new (p) T(move(val)); }
最终代码:
----- 注意. 右值引用不加const
#include <iostream>
#include <cstring>
using namespace std;#include <iostream>
using namespace std;template<typename T>
struct Allocator
{T* allocate(size_t size)// 开辟内存{return (T*)malloc(sizeof(T) * size);}void deallocate(void* p) // 释放内存{free(p);}void construct(T* p, const T& val) //对象构造{new (p) T(val); //定位new/*作用是在一块已经分配好的内存上构造一个对象,而不是通过 new 运算符动态分配内存。p 是一个指针,指向一块预先分配好的内存。T(val) 表示调用类型 T 的构造函数,并传递参数 val。new (p) T(val) 的意思是在 p 指向的内存地址上构造一个 T 类型的对象,并调用构造函数 T(val)。*/}//右值引用 construct 里面的 也需要 move valvoid construct(T* p, T&& val) //对象构造{new (p) T(move(val)); }void destroy(T* p) // 对象析构{p->~T(); //~T()代表T类型的析构函数}
};template<typename T, typename Alloc = Allocator<T>> //Alloc默认是Allocator<T>
class Vector
{
private:T* _first;//数组起始,与数组名T* _last;//数组最后位置的下一个T* _end;//空间的后面位置Alloc _allocator;//定义容器空间配置对象void expand() //二倍扩容{int size = _last - _first;//T* ptmp = new T[2 * size];T* ptmp = _allocator.allocate(2 * size);for (int i = 0; i < size; i++){//ptmp[i] = _first[i];_allocator.construct(ptmp + i, _first[i]);}//delete[]_first;for (T* p = _first; p != _last; ++p){_allocator.destroy(p); //析构_first指针指向的数组的有效元素}_first = ptmp;_last = _first + size;_end = _first + 2 * size;}public:Vector(int size = 10){//_first = new T[size];// 只开辟内存_first = _allocator.allocate(size);_last = _first;_end = _first + size;}~Vector(){//delete[]_first;// 析构有效的元素并释放内存for (T* p = _first; p != _last; ++p){_allocator.destroy(p); //析构_first指针指向的数组的有效元素}_allocator.deallocate(_first); //释放堆上的数组内存_first = _last = _end = nullptr;}Vector(const Vector<T>& src){int size = src._end - src._first;//_first = new T[size];_first = _allocator.allocate(size);int len = src._last - src._first;for (int i = 0; i < len; i++){//_first[i] = src._first[i];_allocator.construct(_first + i, src._first[i]);}_last = _first + len;_end = _first + size;}Vector<T>& operator=(const Vector<T>& src){if (this == &src){return *this;}//delete[]_first;for (T* p = _first; p != _last; ++p){_allocator.destroy(p); //析构_first指针指向的数组的有效元素}int size = src._end - src._first;//_first = new T[size];_first = _allocator.allocate(size);int len = src._last - src._first;for (int i = 0; i < len; i++){//_first[i] = src._first[i];_allocator.construct(_first + i, src._first[i]);}_last = _first + len;_end = _first + size;return *this;}//添加右值引用push_backvoid push_back(T&& val) //向容器末尾添加元素{if (full()){expand();}_allocator.construct(_last, move(val));_last++;}void push_back(const T& val) //向容器末尾添加元素{if (full()){expand();}//*_last++ = val;_allocator.construct(_last, val);_last++;}void pop_back() //向容器末尾删除元素{if (empty()){return;}--_last;_allocator.destroy(_last);}bool full()const{return _last == _end;}bool empty()const{return _last == _first;}T back()const // 返回末尾元素{return *(_last - 1);// *(--_last)错误的, 本函数const方法, 不能修改成员变量, _last-1是偏移量, --会改变last值}
};class String
{
public:// 构造函数String(const char* p = nullptr){if (p != nullptr){_pstr = new char[strlen(p) + 1];cout << "new" << endl;strcpy(_pstr, p);}else{_pstr = new char[1];cout << "new" << endl;*_pstr = '\0';}cout << "String()" << endl;}// 析构函数~String(){delete[] _pstr;cout << "delete" << endl;_pstr = nullptr;cout << "~String()" << endl;}// 左值引用拷贝构造函数String(const String& other){_pstr = new char[strlen(other._pstr) + 1];cout << "new" << endl;strcpy(_pstr, other._pstr);cout << "String(const String)" << endl;}//右值引用拷贝构造String(String&& other) // 换成右值引用 {//临时对象进入_pstr = other._pstr; // 直接指向同一个资源other._pstr = nullptr;cout << "String(String&&)" << endl;}// 左值引用赋值运算符重载String& operator=(const String& other){if (this != &other) // 防止自赋值{delete[] _pstr;cout << "delete" << endl;_pstr = new char[strlen(other._pstr) + 1];cout << "new" << endl;strcpy(_pstr, other._pstr);}cout << "operator=" << endl;return *this;}//右值引用赋值运算符重载String& operator=(String&& other){if (this != &other) // 防止自赋值{delete[] _pstr;cout << "delete" << endl;_pstr = other._pstr; // 直接指向同一个资源other._pstr = nullptr;}cout << "operator=&&" << endl;return *this;}加法运算符重载--未优化, 多了一次new,delete//String operator+(const String& other) const//{// char* newtmp = new char[strlen(_pstr) + strlen(other._pstr) + 1];// strcpy(newtmp, _pstr);// strcat(newtmp, other._pstr);// String newString(newtmp);// delete[]newtmp;// return newString;//}// 加法运算符重载--小优化后String operator+(const String& other) const{String newString;newString._pstr = new char[strlen(_pstr) + strlen(other._pstr) + 1];cout << "new" << endl;strcpy(newString._pstr, _pstr);strcat(newString._pstr, other._pstr);return newString;}// 比较运算符重载bool operator>(const String& other) const{return strcmp(_pstr, other._pstr) > 0;}bool operator<(const String& other) const{return strcmp(_pstr, other._pstr) < 0;}bool operator==(const String& other) const{return strcmp(_pstr, other._pstr) == 0;}// 长度方法size_t length() const{return strlen(_pstr);}// 下标运算符重载char& operator[](size_t index){return _pstr[index];}const char& operator[](size_t index) const{return _pstr[index];}// 输出运算符重载friend std::ostream& operator<<(std::ostream& os, const String& str){os << str._pstr;return os;}// 输入运算符重载friend std::istream& operator>>(std::istream& is, String& str){char buffer[1024];is >> buffer;str = String(buffer);return is; // 支持链式操作 例如: cin>>a>>b,第一次cin>>a返回cin, 即后面的变为 cin>>b}const char* c_str()const { return _pstr; }private:char* _pstr;
};String GetString(String& str)
{const char* pstr = str.c_str();String tmpstr(pstr);return tmpstr; // 不开启RVO时, main函数这里会拷贝构造临时对象//return String(pstr);
}int main()
{String str1 = "aaaaaaaaaa";Vector<String> vec;vec.push_back(str1); //vec.push_back(String("bbbb"));return 0;
}
new
String()
new
String(const String)
new
String()
String(String&&)
delete
~String()
delete
~String()
delete
~String()
delete
~String()
forward完美转发内容
引用折叠
引用折叠(Reference Collapsing)是 C++11 引入的一个规则,用于处理模板和类型推导中涉及引用的复杂情况。它是实现完美转发(Perfect Forwarding)和通用引用(Universal Reference)的基础。
template <typename T>
void foo(T&& arg) {// T 的类型和 arg 的类型由引用折叠规则决定
}
- 如果传递一个左值(如
int x; foo(x);
),T
推导为int&
,arg
的类型为int& &&
,折叠为int&
。 - 如果传递一个右值(如
foo(42);
),T
推导为int
,arg
的类型为int&&
。
push_back模板 – 直接替代左右值引用push_back
template<typename Ty> // 函数模板的类型推演 + 引用折叠void push_back(Ty&& val) {if (full()){expand();}_allocator.construct(_last, val); // 这里还有问题, val传进去会被认为是左值_last++;}
forward完美转发
完美转发(Perfect Forwarding)是 C++11 引入的一项重要特性,用于在函数模板中保留参数的值类别(左值或右值),并将其原封不动地传递给其他函数。完美转发的核心目标是避免不必要的拷贝和移动操作,同时正确处理左值和右值。
完美转发通过以下两个特性实现:
-
通用引用(Universal Reference):
- 使用
T&&
作为参数类型,可以根据传入参数的值类别推导出正确的类型(左值引用或右值引用)。
- 使用
-
std::forward
:std::forward
是一个工具函数,用于保留参数的值类别。- 如果参数是左值,
std::forward
返回左值引用。 - 如果参数是右值,
std::forward
返回右值引用。
-
最终,push_back 修改为
template<typename Ty> // 函数模板的类型推演 + 引用折叠 void push_back(Ty&& val) {if (full()){expand();}_allocator.construct(_last, forward<Ty>(val)); // std::forword(val) 类型的完美转发_last++; }
带有完美转发的 完整代码
construct 也可以使用模板, 不写两次
#include <iostream>
#include <cstring>
using namespace std;#include <iostream>
using namespace std;template<typename T>
struct Allocator
{T* allocate(size_t size)// 开辟内存{return (T*)malloc(sizeof(T) * size);}void deallocate(void* p) // 释放内存{free(p);}// void construct(T* p, const T& val) //对象构造
// {
// new (p) T(val); //定位new
// /*
// 作用是在一块已经分配好的内存上构造一个对象,而不是通过 new 运算符动态分配内存。
//
//p 是一个指针,指向一块预先分配好的内存。
//
//T(val) 表示调用类型 T 的构造函数,并传递参数 val。
//
//new (p) T(val) 的意思是在 p 指向的内存地址上构造一个 T 类型的对象,并调用构造函数 T(val)。
//
// */
// }
// 右值引用 construct 里面的 也需要 move val
// //void construct(T* p, T&& val) //对象构造
// //{
// // new (p) T(move(val));
// //}//模板, 引用折叠template<typename Ty>void construct(T* p, Ty&& val) //对象构造{new (p) T(forward<Ty>(val)); // 注意Ty和 T}void destroy(T* p) // 对象析构{p->~T(); //~T()代表T类型的析构函数}
};template<typename T, typename Alloc = Allocator<T>> //Alloc默认是Allocator<T>
class Vector
{
private:T* _first;//数组起始,与数组名T* _last;//数组最后位置的下一个T* _end;//空间的后面位置Alloc _allocator;//定义容器空间配置对象void expand() //二倍扩容{int size = _last - _first;//T* ptmp = new T[2 * size];T* ptmp = _allocator.allocate(2 * size);for (int i = 0; i < size; i++){//ptmp[i] = _first[i];_allocator.construct(ptmp + i, _first[i]);}//delete[]_first;for (T* p = _first; p != _last; ++p){_allocator.destroy(p); //析构_first指针指向的数组的有效元素}_first = ptmp;_last = _first + size;_end = _first + 2 * size;}public:Vector(int size = 10){//_first = new T[size];// 只开辟内存_first = _allocator.allocate(size);_last = _first;_end = _first + size;}~Vector(){//delete[]_first;// 析构有效的元素并释放内存for (T* p = _first; p != _last; ++p){_allocator.destroy(p); //析构_first指针指向的数组的有效元素}_allocator.deallocate(_first); //释放堆上的数组内存_first = _last = _end = nullptr;}Vector(const Vector<T>& src){int size = src._end - src._first;//_first = new T[size];_first = _allocator.allocate(size);int len = src._last - src._first;for (int i = 0; i < len; i++){//_first[i] = src._first[i];_allocator.construct(_first + i, src._first[i]);}_last = _first + len;_end = _first + size;}Vector<T>& operator=(const Vector<T>& src){if (this == &src){return *this;}//delete[]_first;for (T* p = _first; p != _last; ++p){_allocator.destroy(p); //析构_first指针指向的数组的有效元素}int size = src._end - src._first;//_first = new T[size];_first = _allocator.allocate(size);int len = src._last - src._first;for (int i = 0; i < len; i++){//_first[i] = src._first[i];_allocator.construct(_first + i, src._first[i]);}_last = _first + len;_end = _first + size;return *this;}template<typename Ty> // 函数模板的类型推演 + 引用折叠void push_back(Ty&& val) {if (full()){expand();}_allocator.construct(_last, forward<Ty>(val)); // std::forword(val) 类型的完美转发_last++;}添加右值引用push_back//void push_back(T&& val) //向容器末尾添加元素//{// if (full())// {// expand();// }// _allocator.construct(_last, move(val));// _last++;//}//void push_back(const T& val) //向容器末尾添加元素//{// if (full())// {// expand();// }// //*_last++ = val;// _allocator.construct(_last, val);// _last++;//}void pop_back() //向容器末尾删除元素{if (empty()){return;}--_last;_allocator.destroy(_last);}bool full()const{return _last == _end;}bool empty()const{return _last == _first;}T back()const // 返回末尾元素{return *(_last - 1);// *(--_last)错误的, 本函数const方法, 不能修改成员变量, _last-1是偏移量, --会改变last值}
};class String
{
public:// 构造函数String(const char* p = nullptr){if (p != nullptr){_pstr = new char[strlen(p) + 1];cout << "new" << endl;strcpy(_pstr, p);}else{_pstr = new char[1];cout << "new" << endl;*_pstr = '\0';}cout << "String()" << endl;}// 析构函数~String(){delete[] _pstr;cout << "delete" << endl;_pstr = nullptr;cout << "~String()" << endl;}// 左值引用拷贝构造函数String(const String& other){_pstr = new char[strlen(other._pstr) + 1];cout << "new" << endl;strcpy(_pstr, other._pstr);cout << "String(const String)" << endl;}//右值引用拷贝构造String(String&& other) // 换成右值引用 {//临时对象进入_pstr = other._pstr; // 直接指向同一个资源other._pstr = nullptr;cout << "String(String&&)" << endl;}// 左值引用赋值运算符重载String& operator=(const String& other){if (this != &other) // 防止自赋值{delete[] _pstr;cout << "delete" << endl;_pstr = new char[strlen(other._pstr) + 1];cout << "new" << endl;strcpy(_pstr, other._pstr);}cout << "operator=" << endl;return *this;}//右值引用赋值运算符重载String& operator=(String&& other){if (this != &other) // 防止自赋值{delete[] _pstr;cout << "delete" << endl;_pstr = other._pstr; // 直接指向同一个资源other._pstr = nullptr;}cout << "operator=&&" << endl;return *this;}加法运算符重载--未优化, 多了一次new,delete//String operator+(const String& other) const//{// char* newtmp = new char[strlen(_pstr) + strlen(other._pstr) + 1];// strcpy(newtmp, _pstr);// strcat(newtmp, other._pstr);// String newString(newtmp);// delete[]newtmp;// return newString;//}// 加法运算符重载--小优化后String operator+(const String& other) const{String newString;newString._pstr = new char[strlen(_pstr) + strlen(other._pstr) + 1];cout << "new" << endl;strcpy(newString._pstr, _pstr);strcat(newString._pstr, other._pstr);return newString;}// 比较运算符重载bool operator>(const String& other) const{return strcmp(_pstr, other._pstr) > 0;}bool operator<(const String& other) const{return strcmp(_pstr, other._pstr) < 0;}bool operator==(const String& other) const{return strcmp(_pstr, other._pstr) == 0;}// 长度方法size_t length() const{return strlen(_pstr);}// 下标运算符重载char& operator[](size_t index){return _pstr[index];}const char& operator[](size_t index) const{return _pstr[index];}// 输出运算符重载friend std::ostream& operator<<(std::ostream& os, const String& str){os << str._pstr;return os;}// 输入运算符重载friend std::istream& operator>>(std::istream& is, String& str){char buffer[1024];is >> buffer;str = String(buffer);return is; // 支持链式操作 例如: cin>>a>>b,第一次cin>>a返回cin, 即后面的变为 cin>>b}const char* c_str()const { return _pstr; }private:char* _pstr;
};String GetString(String& str)
{const char* pstr = str.c_str();String tmpstr(pstr);return tmpstr; // 不开启RVO时, main函数这里会拷贝构造临时对象//return String(pstr);
}int main()
{String str1 = "aaaaaaaaaa";Vector<String> vec;vec.push_back(str1); //vec.push_back(String("bbbb"));return 0;
}
总结
move 是为了得到 右值类型
forward 是为了 能够正确识别 左值和右值类型—一般配合模板使用,很方便,不需要写那么多重载
模板中的 引用折叠 很好的解决了 重载麻烦的问题
很灵活的!!!