C++11 (一)
一、 C++11的发展历史
C++11是C++ 的第二个主要版本,并且是从 C++98 起的最重要更新。 它引入了大量更改,标准化了既有实践,并改进了对C++程序员可用的抽象。在它最终由IS0在2011年8月12日采纳前,人们曾使用名称“C++0x”,因为它曾被期待在 2010年之前发布。C++03与C++11期间花了8年时间,故而这是迄今为止最长的版本间隔。从那时起,C++有规律地每3年更新一次。
二、新增语法
2.1 { } 语法
- C++11以后想统一初始化方式,试图实现一切对象皆可用 { } 初始化。{} 初始化也叫做列表初始化。内置类型支持,自定义类型也支持,自定义类型本质是类型转换,中间会产生临时对象,最后优化了以后变成直接构造。
- 在初始化的过程中,可以省略掉 =
- C++11 列表初始化的本意是想实现一个大统一的初始化方式,其次它在有些场景下带来了不少便利,如容器 push/insert 多参数构造的对象时,初始化会很方便。
具体用法:
#include <iostream>
#include <vector>struct test {int x;int y;
};int main() {int a = 10;int b = { 20 }; // 传统方式中可以用等号test p = { 1, 2 }; // C++11 前可能会这样初始化std::vector<int> vec;vec.push_back(1);vec.push_back(2);vec.push_back(3);//以下初始化操作等价( {}代替= )//C++11int a{ 10 }; // 列表初始化 int b{20}; // 列表初始化,不需要等号 test p{ 1, 2 };std::vector<int> vec{ 1, 2, 3 }; // 列表初始化容器
}
3.1右值与右值引用
C++98的C++语法中就有引用的语法,而C++11中新增了右值引用语法特性。C++11之后,我们之前学习的引用就叫做左值引用。无论是左值引用还是右值引用,都是给对象取别名。
Type& x; //左值引用
Type&& y; //右值引用
3.1.1左值和右值
什么是左值?什么是右值?
左值:是一个表示数据的表达式(如变量名或解引用的指针),一般具有持久状态,存储在内存中。我们可以获取它的地址,左值可以出现在赋值符号的左边,也可以出现在赋值符号的右边。定义时,
const
修饰符后的左值,不能给它赋值,但是可以取它的地址。如:变量、数组元素、类成员、解引用指针。右值:也是一个表示数据的表达式,要么是字面值常量,要么是表达式求值过程中创建的临时对象等。右值可以出现在赋值符号的右边,但不能出现在赋值符号的左边,右值不能取地址。如:字面量、临时对象、函数返回值、右值引用。
值得一提的是,左值的英文简写为
lvalue
,右值的英文简写为rvalue
。传统上认为它们分别是left value
和right value
的缩写。而在现代C++中,value 被解释为 locator value 的缩写,意指存储在内存中、有明确存储地址且可以取地址的对象;而rvalue
被解释为 read value,指的是那些可以提供数据值,但不可以寻址的对象,例如:临时变量、字面量常量、存储于寄存器中的变量等。也就是说,左值和右值的核心区别就是 能否取地址。
右值类型分类
C++11 以后,进一步对类型进行了划分,右值被划分为纯右值(pure value,简称 prvalue)和将亡值(expiring value,简称 xvalue)。
1. 纯右值(prvalue)
- 纯右值是指那些字面值常量或求值结果相当于字面值,或者是一个不具名的临时对象。
- 示例:
- 字面值常量:
42
、true
、nullptr
- 临时对象:
str.substr(1, 2)
、str1 + str2
- 表达式结果:如
a++
、a + b
等。在 C++11 中,纯右值概念的划分等价于 C++98 中的右值。
2. 将亡值(xvalue)
- 将亡值是指:
- 返回右值引用的函数的调用表达式。(如:return xxx)
- 转换为右值引用的转换函数的调用表达式,如
std::move(x)
或static_cast<X&&>(x)
。3. 泛左值(glvalue)
- 泛左值是左值的一种扩展,包含了将亡值(xvalue)和左值(lvalue)。
下面代码演示常见左值和右值:
#include <iostream>
#include <vector>class test {
public:int a;test(int val) : a(val) {} // 构造函数
};// 函数返回右值
test create() {return test(10); // 返回临时对象
}int main() {// 左值示例int x = 10; //x是左值int arr[3] = { 1, 2, 3 }; // 数组元素是左值test obj(100); // obj 是左值// 右值int y = 10 + 20; // 10 + 20 是右值,返回临时结果test tempObj = create(); // create 返回一个临时对象,create()是右值int&& y = 30; // 30 是右值,y 是右值引用(下文详谈)
}
3.1.2左值引用和右值引用
第一个语句就是左值引用,左值引用就是给左值取别名,第二个就是右值引用,同样的道理,右值引用就是给右值取别名。Type& r1 = x; Type&& rr1 = y;
- 左值引用不能直接引用右值,但是 const 左值引用可以引用右值。
- 右值引用不能直接引用左值,但是右值引用可以引用 move(左值)。
template <class T> typename remove_reference<T>::type&& move (T&&)
- 这是库里面的一个函数模板,本质内部是进行强制类型转换,当然它还涉及一些引用折叠的知识,这个我们后面会细讲。
- 需要注意的是变量表达式都是左值属性,也就意味着一个右值被右值引用绑定后,右值引用变量变量表达式的属性是左值。
- 语法层面看,左值引用和右值引用都是取别名,不开空间。从汇编底层的角度看下面代码中 r1 和 rr1 汇编层实现,底层都是用指针实现的,没什么区别。底层汇编等实现和上层语法表达的意义有时是背离的,所以不要混到一起去理解,互相佐证,这样反而是陷入迷途。
结合下面的两个demo代码来理解:
#include <iostream>
#include <utility> // for std::moveint main() {int x = 10; // x 是左值int&& rr1 = 20; // 20 是右值,但 rr1 是右值引用变量// 虽然 rr1 是右值引用变量,但在表达式中 rr1 是左值std::cout << rr1 << std::endl; // 这里 rr1 表达式是左值// 使用 std::move 将 x 转换为右值,返回右值引用int&& rr2 = std::move(x);// 虽然 rr2 是右值引用变量,但在表达式中 rr2 是左值(也就是上文中 属性是左值)std::cout << rr2 << std::endl; // 这里 rr2 表达式是左值return 0;
}
代码运行结果:
20
10
(二)
int main() {// 左值:可以取地址// 以下的p、b、c、*p、s、s[0]就是常见的左值int* p = new int(0);int b = 1;const int c = b;*p = 10;string s("111111");s[0] = 'x';// 左值引用给左值取别名 (就是普通的应用)int& r1 = b;int*& r2 = p;int& r3 = *p;string& r4 = s;char& r5 = s[0];// 右值:不能取地址double x = 1.1, y = 2.2;// 以下几个10、x + y、fmin(x, y)、string("11111")都是常见的右值10;x + y;fmin(x, y);string("11111"); //(匿名对象)// 右值引用给右值取别名int&& rr1 = 10;double&& rr2 = x + y; //表达式,它会产生一个临时的计算结果 x + y 并没有具名的存储位置,它是一个瞬时的值(右值)double&& rr3 = fmin(x, y);string&& rr4 = string("11111");// 左值引用不能直接引用右值,但是const左值引用可以引用右值const int& rx1 = 10;const double& rx2 = x + y;const double& rx3 = fmin(x, y);const string& rx4 = string("11111");// 右值引用不能直接引用左值,但是右值引用可以引用move(左值)int&& rrx1 = move(b);int*&& rrx2 = move(p);int&& rrx3 = move(*p);string&& rrx4 = move(s);string&& rrx5 = (string&&)s;// int&& rr1 = 10;// 这里要注意的是,rr1的属性是左值,所以不能再被右值引用绑定,除非move一下int& r6 = rr1;//int&& rrx6 = rr1;// (错误)int&& rrx6 = move(rr1);//正确return 0;
}
【总结】
左值引用
左值引用给左值取别名,通常是直接对变量进行引用。注意左值引用不能绑定到右值,除非使用 const
右值的定义和使用
右值不能取地址,它通常是临时值、字面值、表达式结果等。
右值引用与 std::move
转移所有权
右值引用通常用于实现移动语义(通过 std::move
)。你不能直接将左值绑定到右值引用,但可以通过 std::move
将左值转为右值引用,这会“转移”对象的资源(例如,避免不必要的复制)。
右值引用
右值引用给右值取别名。它可以通过 &&
来声明,但只能引用右值,不能引用左值,除非通过 std::move
转移
常量左值引用
常量左值引用 (const T&
) 可以绑定到右值,也可以绑定到左值。这是因为常量左值引用不会修改被引用的对象,因此可以接受临时对象(右值)。
右值引用与 std::move
转移所有权
右值引用通常用于实现移动语义(通过 std::move
)。你不能直接将左值绑定到右值引用,但可以通过 std::move
将左值转为右值引用,这会“转移”对象的资源(例如,避免不必要的复制)。
右值引用与左值引用的互操作
右值引用不能直接引用左值,但可以使用 std::move
将左值转为右值引用。
3.1.3 左值和右值的参数匹配问题
C++98中,我们实现一个const左值引用作为参数的函数,那么实参传递左值和右值都可以匹配。
C++11以后,分别重载左值引用、const左值引用、右值引用作为形参的f函数,那么实参是左值会匹配f(左值引用),实参是const左值会匹配f(const左值引用),实参是右值会匹配f(右值引用)。
右值引用变量在用于表达式时属性是左值,这个设计这里会感觉很奇怪,下一小节我们讲右值引用的使用场景时,就能体会这样设计的价值了。
结合以下代码来说明右值引用的传参问题:
template<class T>
void func(const T& x)
{}
//三个不同的函数重载
void func(int& x)
{std::cout << "左值引用重载 func(" << x << ")\n";
}
void func(const int& x)
{std::cout << "到 const 的左值引用重载 func(" << x << ")\n";
}void func(int&& x)
{std::cout << "右值引用重载 func(" << x << ")\n";
}int main()
{/*int&& rr1 = 10;int a = 20;int& r2 = a;*/int i = 1;const int ci = 2;func(i); // 调用 f(int&)func(ci); // 调用 f(const int&)func(3); // 调用 f(int&&),如果没有 f(int&&) 重载则会调用 f(const int&)func(std::move(i)); // 调用 f(int&&)// 右值引用变量在用于表达式时是左值// 右值引用本身的属性是左值int&& x = 1;func(x); // 调用 f(int& x)func(std::move(x)); // 调用 f(int&& x)return 0;
}
程序运行结果:
左值引用重载 func(1)
到 const 的左值引用重载 func(2)
右值引用重载 func(3)
右值引用重载 func(1)
左值引用重载 func(1)
右值引用重载 func(1)
分析:
-
template<class T> void func(const T& x) {}
是一个模板函数,用于匹配所有类型的 const 左值引用。 -
void func(int& x)
用于左值引用重载,当传递一个左值时调用此函数。 -
void func(const int& x)
用于 const 左值引用重载,当传递一个 const 左值时调用此函数。 -
void func(int&& x)
用于右值引用重载,当传递一个右值时调用此函数。
int&& x = 1;
func(x); // 调用 func(int& x)
func(std::move(x)); // 调用 func(int&& x)
-
x
是右值引用变量,但在表达式func(x)
中,x
被视为左值,所以调用void func(int& x)
。 -
std::move(x)
将x
转换为右值,匹配void func(int&& x)
。
**此部分只做描述说明,下一小节才真正介绍此语法的重大意义和使用场景。
3.2 移动语义
回顾左值应用的局限性、使用场景
左值引用主要使用场景是在函数中左值引用传参和左值引用传返回值时减少拷贝,同时还可以修改实参和修改返回对象的价值。左值引用已经解决大多数场景的拷贝效率问题,但是有些场景不能使用传左值引用返回,如下
addStrings
函数。C++98 中的解决方案只能是被迫使用输出型参数解决。//此函数 模拟两个大整数字符串相加的过程 string addStrings(string num1, string num2) {string str;int end1 = num1.size() - 1, end2 = num2.size() - 1;int next = 0; // 进位while (end1 >= 0 || end2 >= 0){int val1 = end1 >= 0 ? num1[end1--] - '0' : 0; // 获取num1的当前位int val2 = end2 >= 0 ? num2[end2--] - '0' : 0; // 获取num2的当前位int ret = val1 + val2 + next; // 当前位相加并加上进位next = ret / 10; // 更新进位ret = ret % 10; // 当前位的结果str += ('0' + ret); // 将当前位的结果添加到str末尾}if (next == 1)str += '1'; // 如果还有进位,添加到结果中reverse(str.begin(), str.end()); // 反转结果字符串return str; // 返回最终结果字符串,因为出了函数调用栈帧会销毁//str是函数内部创建的对象,str作返回值,str的值会先拷贝到临时对象// 然后栈帧销毁 str销毁,只有临时对象返回 }
str
是在函数内部创建的对象,存储在栈帧中。函数执行完毕后,栈帧将被销毁,str
也会随之销毁。(不考虑优化情况)返回值会先拷贝到一个临时对象,然后在函数结束后栈帧销毁,str
被销毁。最终返回给调用者的是这个临时对象。- 所以拷贝到临时对象就是多余的开销,会占用更多的空间。所以C++旨在消除不必要的拷贝,创建右值引用。
那么C++11以后这里可以使用右值引用做返回值解决吗?显然是不可能的,因为这里的本质是返回对象是一个局部对象,函数结束这个对象就析构销毁了,右值引用返回也无法改变对象已经析构销毁的事实。
那么既然右值引用依法无法解决返回局部对象问题,右值引用的价值在哪呢?
下文详细解释。
3.2.1 移动构造和移动赋值
移动构造函数是一种构造函数,类似拷贝构造函数。移动构造函数要求第一个参数是该类类型的引用,但不同的是,要求这个参数是右值引用。如果还有其他参数,额外的参数必须有缺省值。
移动赋值是一个赋值运算符的重载,它与拷贝赋值构成函数重载。类似拷贝赋值函数,移动赋值函数要求第一个参数是该类类型的引用,但不同的是,要求这个参数是右值引用。
对于像
string
/vector
这样的深拷贝类或者包含深拷贝成员变量的类,移动构造和移动赋值才有意义。因为移动构造和移动赋值的第一个参数都是右值引用类型,它们的本质是要“窃取”引用的右值对象的资源,而不是像拷贝构造和拷贝赋值那样去拷贝资源,从而提高效率。
结合代码来理解以上内容:
class String
{
public:typedef char* iterator;typedef const char* const_iterator;iterator begin() { return _str; }iterator end(){return _str + _size;}const_iterator begin() const{return _str;}const_iterator end() const{return _str + _size;}void swap(String& ss){std::swap(_str, ss._str);std::swap(_size, ss._size);std::swap(_capacity, ss._capacity);}//普通构造函数String(const char* str = ""):_size(strlen(str)), _capacity(_size){cout << "string(char* str)-构造" << endl;_str = new char[_capacity + 1];strcpy(_str, str);}// 拷贝构造String(const String& s):_str(nullptr){cout << "string(const string& s) -- 拷贝构造" << endl;reserve(s._capacity);for (auto ch : s){push_back(ch);}}//移动构造String(String&& s){cout << "string(string&& s) -- 移动构造" << endl;// 转移掠夺你的资源swap(s);}//普通赋值String& operator=(const String& s){cout << "string& operator=(const string& s) -- 拷贝赋值" <<endl;if (this != &s){_str[0] = '\0';_size = 0;reserve(s._capacity);for (auto& ch : s){push_back(ch);}}return *this;}// 移动赋值String& operator=(String&& s){cout << "string& operator=(string&& s) -- 移动赋值" << endl;swap(s);return *this;}~String(){//cout << "~string() -- 析构" << endl;delete[] _str;_str = nullptr;}char& operator[](size_t pos){assert(pos < _size);return _str[pos];}void reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];if (_str){strcpy(tmp, _str);delete[] _str;}_str = tmp;_capacity = n;}}void push_back(char ch){if (_size >= _capacity){size_t newcapacity = _capacity == 0 ? 4 : _capacity *2;reserve(newcapacity);}_str[_size] = ch;++_size;_str[_size] = '\0';}String& operator+=(char ch){push_back(ch);return *this;}const char* c_str() const{return _str;}size_t size() const{return _size;}void str_printf(){if (_str) {cout << "str=" << _str << " size=" << _size << " capacity=" << _capacity << endl; }else {cout << "str=nullptr size=" << _size << " capacity=" << _capacity << endl;}}
private:char* _str = nullptr;size_t _size = 0;size_t _capacity = 0;
};int main() {String str1("Hello");//移动构造前cout << "移动构造前:";str1.str_printf();String str2 = move(str1); // 调用移动构造函数cout << "移动构造后:";str1.str_printf();cout <<endl;String str3;str3 = std::move(str2); // 调用移动赋值运算符return 0;
}
代码运行结果:
string(char* str)-构造
移动构造前:str=Hello size=5 capacity=5
string(string&& s) -- 移动构造
移动构造后:str=nullptr size=0 capacity=0string(char* str)-构造
string& operator=(string&& s) -- 移动赋值
下面我们来详细分析一下移动构造和移动赋值里面做了什么?
1.移动构造:
int main() {String str1("Hello");String str2 = move(str1); // 调用移动构造函数//move的它会将 str1 转换为一个右值引用。//表示 str1 的资源可以被“移动”到另一个对象,而不是被复制。return 0;
}void swap(String& ss)
{std::swap(_str, ss._str);std::swap(_size, ss._size);std::swap(_capacity, ss._capacity);
}//移动构造
String(String&& s)//接受一个右值引用参数 String&& s。
{cout << "string(string&& s) -- 移动构造" << endl;// 转移掠夺你的资源swap(s); // 将参数 s 中的资源转移到当前对象中// 将正在构造的新对象的 全空的资源换到s中。
}
移动构造函数:
-
String str2 = move(str1);
:-
str1
是一个右值,触发移动构造函数。 -
str1
的内部资源(如_str
指针)被转移到str2
。 -
str1
资源转移后变为空(_str
置为nullptr,_size和_capacity
置为0
)
-
对应程序运行结果:
string(char* str)-构造
str1 移动构造前:str=Hello size=5 capacity=5
string(string&& s) -- 移动构造
str1 移动构造后:str=nullptr size=0 capacity=0
str1的资源也就被转换到str2中了。
移动赋值函数:
int main() {String str1("Hello");String str2 = move(str1); // 调用移动构造函数//move的它会将 str1 转换为一个右值引用。//这一转换表示 str1 的资源可以被“移动”到另一个对象,而不是被复制。String str3; //默认构造出str3str3 = std::move(str2); // 调用移动赋值运算符return 0;
}void swap(String& ss)
{std::swap(_str, ss._str);std::swap(_size, ss._size);std::swap(_capacity, ss._capacity);
}// 移动赋值
String& operator=(String&& s)
{cout << "String& operator=(String&& s) -- 移动赋值" << endl;// 转移掠夺你的资源swap(s);return *this;
}
移动赋值和移动构造的执行其实很相似:(区别:赋值运算发生在2个已经构造出来的对象,拷贝移动构造发生在一个已构造,另外一个还未构造,现在构造的对象)
移动赋值运算符:
-
str3 = std::move(str2);
:-
str2
是一个右值,触发移动赋值运算符。 -
str2
的内部资源(_str
指针,_size和_capacity
)被转移到str3
。 -
str2
资源转移后变为空(_str
置为nullptr,_size和_capacity
置为0
)。
-
对比拷贝构造和移动构造:
拷贝构造:
- 拷贝构造会对资源执行深拷贝,即分配新的内存,并复制源对象的所有数据
- 效率较低:因为需要分配新资源并复制数据,这涉及到内存分配和额外的拷贝操作,尤其当对象包含大量资源(如动态数组、大量成员变量等)时,开销显著。
移动构造:
- 用于从临时对象(右值)或资源即将销毁的对象中构造新对象。典型实现中,移动构造会直接**“窃取”源对象的资源指针**,并将源对象的资源指针置空。
- 效率较高:避免了不必要的资源分配和复制,仅仅通过指针/句柄的转移操作即可完成对象的构造,极大地减少了开销。
- 资源转移:目标对象“接管”源对象的资源,源对象的资源被置为空(无效状态)
3.2.2 移动语义实用场景
右值对象的构造:拷贝与移动构造
在现代 C++ 中,右值对象的构造可能涉及拷贝构造或移动构造。移动构造能够显著提高效率,因为它避免了不必要的深拷贝操作,而是直接转移资源。但具体调用拷贝还是移动构造,与编译器优化和上下文相关。
编译器优化现象
在以下场景中,编译器会对构造和移动操作进行优化:
优化前:
- 左边(未优化)情况下,构造顺序可能会经历两次移动构造:
- 构造一个临时对象。
- 临时对象移动构造到目标对象。
- 返回目标对象。
优化后:
- 编译器可以将多次构造和移动操作合并为一次直接构造:
- VS2019 Release 和 VS2022 编译器会直接在目标位置上构造对象,避免临时对象和多次构造操作。
class MyClass {std::string data;
public:// 构造函数MyClass(const std::string& str) : data(str) {std::cout << "构造: " << data << '\n';}// 拷贝构造函数MyClass(const MyClass& other) : data(other.data) {std::cout << "Copy 拷贝构造: " << data << '\n';}// 移动构造函数/*MyClass(MyClass&& other) noexcept : data(std::move(other.data)) {std::cout << "Move 移动构造: " << data << '\n';}*/
};MyClass createObject() {MyClass temp("Test Object"); // 临时对象return temp; // 返回临时对象
}int main() {std::cout << "未优化:\n";MyClass obj1 = createObject(); // 未优化情况下观察行为//std::cout << "\n已优化:\n";在优化模式下运行观察行为//MyClass obj2 = createObject();return 0;
}
程序输出:
未优化:
构造: Test Object
Copy 拷贝构造: Test Object
实际上这里编译器还是做了一点点优化的,如果完全没优化的过程如下图:
如果类中有提供移动语义:
#include <iostream>
#include <string>class MyClass {std::string data;
public:// 构造函数MyClass(const std::string& str) : data(str) {std::cout << "构造: " << data << '\n';}// 拷贝构造函数MyClass(const MyClass& other) : data(other.data) {std::cout << "Copy 拷贝构造: " << data << '\n';}// 移动构造函数MyClass(MyClass&& other) noexcept : data(std::move(other.data)) {std::cout << "Move 移动构造: " << data << '\n';}// 移动赋值函数MyClass& operator=(MyClass&& other) noexcept {if (this != &other) { // 防止自赋值data = std::move(other.data);std::cout << "Move 移动赋值: " << data << '\n';}return *this;}// 拷贝赋值构造函数MyClass& operator=(const MyClass& other) {if (this != &other) {data = other.data;std::cout << "Copy 拷贝赋值: " << data << '\n';}return *this;}
};MyClass createObject() {MyClass temp("Test Object"); // 临时对象return temp; // 触发移动构造,
}int main() {std::cout << "未优化:\n";MyClass obj1 = createObject(); // 未优化情况下观察行为//std::cout << "\n已优化:\n";在优化模式下运行观察行为//MyClass obj2 = createObject();return 0;
}
分析一下核心代码:
MyClass createObject() {MyClass temp("Test Object"); // 临时对象return temp; // 触发移动构造,
}int main() {std::cout << "未优化:\n";MyClass obj1 = createObject(); // 未优化情况下观察行为//std::cout << "\n已优化:\n";在优化模式下运行观察行为//MyClass obj2 = createObject();return 0;
}
temp
是一个 局部变量,在 createObject()
函数中定义。根据 C++ 语义,temp
作为局部变量是 左值,因为它是可以通过名字引用的,且具有明确的内存位置。
但到了语句:return temp;
:此时,temp
作为函数的返回值,是一个临时的对象。虽然 temp
是局部变量(左值),但它在 return
语句中被传递给了函数外部,它的生命周期延续到 createObject()
返回的调用者中。由于 temp
是在函数内部创建的,且它的生命周期只在该函数中存在,因此它是作为 返回值 被“返回”给外部代码,而编译器会将其当作 右值 来处理。
- 这时,
temp
被视作 右值,因为它是即将离开函数作用域的临时对象,没有持续的存在。
程序运行结果:
未优化:
构造: Test Object
Move 移动构造: Test Object
结果分析:
终结:
-
拷贝构造的开销:
- 时间开销:拷贝构造涉及逐一复制源对象的数据,如果对象的成员比较大(例如包含动态分配的内存),那么拷贝的时间开销会比较大。
- 空间开销:需要为返回的对象分配新的内存来存储数据,导致额外的内存消耗。
-
移动构造的开销:
- 时间开销:移动构造的时间开销相对较低,因为它仅仅是将资源的所有权从源对象转移给目标对象(例如,交换指针、缓冲区等),而不涉及数据的逐一复制。
- 空间开销:移动构造通常不需要额外的内存分配,因为它只需要更新指针和所有权管理。
深拷贝的自定义类型:如vector/string/map....实现移动构造和移动赋值是有很大的价值的。
浅拷贝的自定义类型:如Date/pair<int,int>...不需要实现移动构造和移动赋值也没有问题。
相关文章:

C++11 (一)
一、 C11的发展历史 C11是C 的第二个主要版本,并且是从 C98 起的最重要更新。 它引入了大量更改,标准化了既有实践,并改进了对C程序员可用的抽象。在它最终由IS0在2011年8月12日采纳前,人们曾使用名称“C0x”,因为它曾…...

系统性能优化
一、概述 性能优化的目标:是提高系统或应用程序的响应时间、吞吐量、cpu、内存、磁盘IO、网络、流量、JVM、Tomcat、DB等方面的性能指标。 性能优化需要有一些技巧:对于整个产品或项目而言,比如可以从前端优化、后端优化、架构优化、高并发…...

IMX6ULL开发板挂载 Ubuntu 的 NFS 目录,并以交叉编译得到的hello程序进行测试
首先参考博文 https://blog.csdn.net/wenhao_ir/article/details/144404637 使得IMX6ULL开发板、PC机上的USB网卡、VMware中的Ubuntu能互相Ping 通 然后开始将Ubuntu 的 NFS 目录挂载到Ubuntu中。 为什么挂载? 答:其实是把 Ubuntu中的某个目录通过NFS网…...
Xcode模拟器运行报错:The request was denied by service delegate
Xcode模拟器运行报错:The request was denied by service delegate 造成的原因: (1)新的苹果M系列芯片的Mac电脑 (2)此电脑首次安装启动Xcode的应用程序 (3)此电脑未安装Rosetta 2 解决方法: …...

ubuntu18.04配置实时内核
ubuntu系统:18.04 当前内核:5.4.0-84-generic 待安装实时内核: 5.6.19-rt11 1、查看当前版本 uname -r 2、下载内核与补丁 一种方式从官网自己下载 官方内核下载地址官方补丁下载地址阿里镜像内核下载地址(速度快࿰…...

Unity中Mesh重叠顶点合并参考及其应用
在Unity中,如果将一个模型文件(比如从max里面导出一个fbx文件)导入到编辑器中之后,Unity会把所有在原来在面列表中公用的顶点复制一份,保证每个三角形使用的顶点都是单独的,不与其它三角形共用顶点…...

倚光科技助力自由曲面设计与加工
近年来,自由曲面因其在光学、汽车、航空航天等领域的广泛应用,受到设计师和工程师的高度关注。自由曲面作为一种具有更高自由度的非球面透镜,能够在光学系统中实现更加精确的光线控制,优化像差校正,并且在满足功能需求…...

PWM调节DCDC参数计算原理
1、动态电压频率调整DVFS SOC芯片的核电压、GPU电压、NPU电压、GPU电压等,都会根据性能和实际应用场景来进行电压和频率的调整。 即动态电压频率调整DVFS(Dynamic Voltage and Frequency scaling),优化性能和功耗。 比如某SOC在…...

[Pro Git#3] 远程仓库 | ssh key | .gitignore配置
目录 1. 分布式版本控制系统的概念 2. 实际使用中的“中央服务器” 3. 远程仓库的理解 4. 新建远程仓库 5. 克隆远程仓库 6. 设置SSH Key 实验 一、多用户协作与公钥管理 二、克隆后的本地与远程分支对应 三、向远程仓库推送 四、拉取远程仓库更新 五、配置Git忽略…...

Freertos任务切换
一、操作系统进行任务切换的时机: 采用信号量实现任务的互斥: 二、FreeRTOS 任务切换场合 PendSV 中断的时候提到了上下文(任务)切换被触发的场合: ● 可以执行一个系统调用 ● 系统滴答定时器(SysTick)中断。 1、执行系统调用 执行系统…...
go开发中interface和方法接收器的使用
Go 语言中的接口和方法接收器学习 Go 中的 interface 就像是一个神奇的魔法杖,能让你轻松地将不同的类型拉到同一个阵营里。与其他语言的接口不同,Go 的接口无需显式声明“我实现了你”,只要你满足了接口规定的方法,Go 就会自动认…...

vue3-tp8-Element:对话框实现
效果 参考框架 Dialog 对话框 | Element Plus 具体实现 一、建立view页面 /src/views/TestView.vue 二、将路径写入路由 /src/router/index.js import { createRouter, createWebHistory } from vue-router import HomeView from ../views/HomeView.vueconst router create…...

高中数学:随机变量-正态分布
文章目录 一、连续性随机变量二、大致图像三、正态分布图像及解析式图像特点均值与方差公式正态分布各区域概率 一、连续性随机变量 二、大致图像 三、正态分布图像及解析式 图像特点 均值与方差公式 正态分布各区域概率...

游戏引擎学习第47天
仓库: https://gitee.com/mrxiao_com/2d_game 昨天我们花了一点时间来修复一个问题,但基本上是在修复这个问题的过程中,我们决定添加一个功能,那就是在屏幕上控制多个实体。所以如果我有一个手柄,我可以添加另一个角色࿰…...

Git 仓库托管教程
git远程仓库 常用的远程仓库-->托管服务:github、码云、gitlab等 github需要魔法上网,速度较慢因为在国外且仅仅支持Git,如果不是Git项目是不支持的;码云--gitee国内的代码托管平台,服务器在国内速度快一些&#…...

基于51单片机的简易时钟/定时器闹钟proteus仿真
地址: https://pan.baidu.com/s/1uez4cwZuXpchmihmRqnLEg 提取码:1234 仿真图: 芯片/模块的特点: AT89C52/AT89C51简介: AT89C52/AT89C51是一款经典的8位单片机,是意法半导体(STMicroelectro…...
Jackson @JsonProperty 注解
1. 概述 Jackson 是一个流行的Java库,用于将Java对象转换为JSON格式以及从JSON反序列化回Java对象。一种常见的需求是在序列化为JSON或从JSON反序列化时自定义字段的命名。Jackson 的 JsonProperty 注解正好满足了这一需求。 JsonProperty 注解概览 JsonProperty…...
【Excel学习记录】02-单元格格式设置
1.单元格格式工具美化表格 单元格格式位置 选中单元格,右键→设置单元格格式 合并居中 跨越合并 字体类型、大小、颜色、填充底纹、边框 斜线 软回车:alt enter 格式刷 2.单元格数字格式 格式不影响数值,只是展示形式 日期本质也是数…...

支持自定义离线地图地理区域,查询组件及数据源功能增强,DataEase开源BI工具v2.10.3 LTS发布
2024年12月9日,人人可用的开源BI工具DataEase正式发布v2.10.3 LTS版本。 这一版本的功能变动包括:数据源方面,API数据源和Excel数据源支持对字段类型和长度进行设置;图表方面,离线类地图支持自定义地理区域设置&#…...
LF CRLF
这个提示的含义是:Git 检测到你当前的 file3.txt 文件中使用了 LF(换行符,Line Feed,\n) 作为换行符,但在你系统的 Git 配置中,指定要将其转换为 CRLF(回车换行,Carriage…...
GIT(AI回答)
在Git中,git push 命令主要用于将本地分支的提交推送到远程仓库(如GitHub、GitLab等)。如果你希望将本地分支的改动同步到另一个本地分支,这不是 git push 的设计目的。以下是正确的替代方法: 方法1࿱…...
【HarmonyOS 5】 社交行业详解以及 开发案例
HarmonyOS 5通过分布式能力、响应式框架及AI技术,重构社交应用的交互范式,以下是分领域解析: 🧏 一、无障碍社交创新 听障人士实时通讯辅助 语音文字双向转译功能:对方语音实时转为文字显示,用户…...

从 ClickHouse、Druid、Kylin 到 Doris:网易云音乐 PB 级实时分析平台降本增效
网易云音乐基于 Apache Doris 替换了早期架构中 Kylin、Druid、Clickhouse、Elasticsearch、HBase 等引擎,统一了实时分析架构,并广泛应用于广告实时数仓、日志平台和会员报表分析等典型场景中,带来导入性能提升 3~30 倍ÿ…...
【生活】程序员防猝si指南
note 一、定期体检二、均衡饮食,多食用对心脏有保护作用的食物三、每周运动四、减压五、保证睡眠六、戒烟限酒7、控制血压8、警惕流感攻击心脏9、关注牙齿健康10、不要抵触吃药 文章目录 note一、定期体检二、均衡饮食,多食用对心脏有保护作用的食物三、…...

【工具-Wireshark 抓包工具】
工具-Wireshark 抓包工具 ■ Wireshark 抓包工具■ 通过IP指定查看■■ ■ Wireshark 抓包工具 抓包工具】win 10 / win 11:WireShark 下载、安装、使用 Wireshark下载 阿里云镜像 ■ 通过IP指定查看 ■ ■...

大模型如何选型?嵌入模型如何选型?
欢迎来到啾啾的博客🐱。 记录学习点滴。分享工作思考和实用技巧,偶尔也分享一些杂谈💬。 有很多很多不足的地方,欢迎评论交流,感谢您的阅读和评论😄。 目录 引言模型优劣认知与模型选择大模型(L…...

IDEA集成JRebel插件,实现实时热部署
系列文章目录 文章目录 系列文章目录一、JRebel是什么?1.1、对比传统开发流程1.2、JRebel特性以及优势 二、IDEA集成JRebel三、IDEA以JRebel运行报错处理四、IDEA以JRebel运行演示实时热部署 一、JRebel是什么? JRebel 是一款针对 Java 开发的热部署工具…...

Docker容器部署elasticsearch8.*与Kibana8.*版本使用filebeat采集日志
第 1 步:使用 Docker Compose 部署 Elasticsearch 和 Kibana 首先,我们需要创建一个 docker-compose.yml 文件来定义和运行 Elasticsearch 和 Kibana 服务。这种方式可以轻松管理两个容器的配置和网络。 创建 docker-compose.yml 文件 在一个新的文件夹…...

【力扣链表篇】19.删除链表的倒数第N个节点
题目: 给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。 示例 1: 输入:head [1,2,3,4,5], n 2 输出:[1,2,3,5]示例 2: 输入:head [1], n 1 输出:[]…...
Vue解决开发环境 Ajax 跨域问题
一、前言 在使用 Vue 进行前后端分离开发时,前端通常运行在本地开发服务器(如 http://localhost:8080),而后端接口可能部署在其他域名或端口下(如 http://api.example.com:3000)。这时就可能出现 跨域&…...