对象被优化后, 才是高效的c++编程
文章目录
注意, 这节讲的, 有很多 和现代编译器的结果不同, 注意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了, 非临时对象, 普通构造 , 不析构, 没有delete Test* p2 = new Test[2]; // 10. 数组, 两次普通构造 , 不析构, 没有delete //Test* p3 = &Test(80, 80); // 11. 指针, 普通构造 , 但是出了这句 就析构了 成为 悬空指针 会被警告, 无法编译执行 const Test& p4 = Test(90, 90); //12. 引用, 普通构造, 出了这句不析构, 出了函数析构 delete p1; // 13. 析构 p1 delete[] 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_back
void 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_back
void 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 val
void 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_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;
}
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 是为了 能够正确识别 左值和右值类型—一般配合模板使用,很方便,不需要写那么多重载
模板中的 引用折叠 很好的解决了 重载麻烦的问题
很灵活的!!!