C++11 左值、右值和移动语义

一、结论 C 中将数据类型分为左值和右值,在 C++11 中又将右值概念更为细致的分为将亡值(xvalue)和纯右值(prvalue)。 将亡值是 C++11 新增的跟右值引用相关的表达式,通常是将要被移动的对象(移为他用),比如返回右值引用 T&& 的函数返回值、std::move 的返回值。 从性能上讲,左右值引用没有区别,传参使用左右值引用都可以避免拷贝。 右值引用可以直接指向右值,也可以通过 std::move 指向左值;而左值引用只能指向左值(const左值引用也能指向右值)。 作为函数形参时,右值引用更灵活。虽然 const 左值引用也可以做到左右值都接受,但它无法修改,有一定局限性。 可移动对象在需要拷贝且被拷贝者之后不再被需要的场景,建议使用 std::move 触发移动语义,提升性能。 我们可以在自己的类中实现移动语义,避免深拷贝,充分利用右值引用和 std::move 的语言特性。 std::move 本身只做类型转换,对性能无影响。 std::forward 同样也是做类型转换且更强大,move 只能转出来右值,forward 既可以转成右值,又可以转成左值。 函数最好不要返回函数体内局部变量的左值引用或右值引用。 二、注意事项 左值引用是具名变量值的别名 右值引用是不具名(匿名)变量的别名 引用是变量的别名,由于右值没有地址,没法被修改,所以左值引用无法指向右值,但是,const左值引用是可以指向右值的 const int &ref_a = 5; int a = 5; int &ref_a = a; // 左值引用指向左值,编译通过 int &ref_a = 5; // 左值引用指向了右值,会编译失败 右值引用的标志是 &&,顾名思义,右值引用专门为右值而生,可以指向右值,不能指向左值 int&& ref_a_right = 5; // ok int a = 5; int&& ref_a_left = a; // 编译不过,右值引用不可以指向左值 ref_a_right = 6; // 右值引用的用途:可以修改右值 事实上 std::move 移动不了什么,唯一的功能是把左值强制转化为右值,让右值引用可以指向左值。其实现等同于一个类型转换:static_cast<T&&>(lvalue)。 所以,单纯的 std::move(xxx) 不会有性能提升,从这个角度来讲,右值引用和左值引用的功能相似,都是原始变量的别名,至于移动后原始变量是否可用,取决于被移动的数据类型对于移动语义的具体实现。...

February 23, 2022 · 4 min · Rick Cui

C++11 auto_ptr 被废弃与 unique_ptr

一、auto_ptr 特点 支持拷贝构造 支持赋值拷贝 支持 operator->/operator* 解引用 支持指针变量重置 保证指针持有者唯一(涉及所有权转移) 二、问题一:使用数组存储 auto_ptr std::vector<std::auto_ptr<People>> peoples; // 这里实例化多个people并保存到数组中 ... std::auto_ptr<People> one = peoples[5]; ... std::cout << peoples[5]->get_name() << std::endl; 原因在于 std::auto_ptr 支持 operator=,为了确保指针所有者唯一,这里转移了所有权,people[5] 变成了 null 三、问题二、函数传参 auto_ptr 类型 void do_somthing(std::auto_ptr<People> people){ // 该函数内不对people变量执行各种隐式/显示的所有权转移和释放 ... } std::auto_ptr<People> people(new People("jony")); do_something(people); ... std::cout << people->get_name() << std::endl; 原因在于 std::auto_ptr支持拷贝构造,为了确保指针所有者唯一,这里转移了所有权 四、unique_ptr 在11中,可以支持右值以及移动语义了,此时可以完全匹配auto_ptr的所有权管理,新增了 std::unique_ptr。std::unique_ptr 不仅加入了移动语义的支持,同时也关闭了左值拷贝构造和左值赋值功能!杜绝了上述场景的出现!但是,此时,需要使用其他的方案了。比如场景一中,std::unique_ptr类型变量不能使用vector保存了! 所以 std::auto_ptr 废弃了,由 std::unique_ptr 代替! class Person{ public: int getAge()const{ return m_age; } void setAge(int age){ m_age = age; } private: int m_age{20}; }; unique_ptr<Person> Change(unique_ptr<Person> p){ p->setAge(30); return p; } int main() { unique_ptr<Person> pP(new Person); cout << pP->getAge() << endl; // unique_ptr<Person> pP2 = Change(pP); // error: unique_ptr 删除了拷贝构造,所以需要使用 move 关键字转移所有权 unique_ptr<Person> pP2 = Change(move(pP)); // pP = pP2; // error:unique_ptr 删除了 = 赋值构造 cout << "-------------" << endl; cout << pP2->getAge() << endl; cout << "-------------" << endl; if(pP == nullptr){ cout << "pP 变量已失效" << endl; } return 0; } 输出:...

February 22, 2022 · 1 min · Rick Cui

C++11 mutable 关键字

一、修饰类成员变量 此关键字只能应用于类的非静态和非常量数据成员,mutable 是为了让 const 对象的某些数据成员可以被修改。static 是类的成员,不属于对象,常对象和常函数只会限制类的成员变量修改,所以类的 static 数据成员不需要 mutable 修饰,在常对象和常函数中也能被修改。 class Person{ public: int getAge() const{ m_count++; s_count++; return m_age; } int getCount()const{ return m_count; } private: int m_age{20}; mutable int m_count{0}; public: static int s_count; }; int Person::s_count = 0; int main() { Person p; p.getAge(); p.getAge(); p.getAge(); cout << p.getCount() << endl; // 3 cout << p.s_count << endl; // 3 return 0; } 二、修饰匿名函数 表示可以修改按值传入的变量的副本(不是值本身),类似于不带 const 关键字的形参。使用 mutable 关键字后对按值传入的变量进行的修改,不会将改变传递到 Lambda 表达式之外。如果不加 mutable 关键字,按值传入的变量是只读的,即使在 Lambda 表达式内部也不可修改...

February 22, 2022 · 1 min · Rick Cui

C++11 auto 与 decltype 关键字

一、进行自动类型推导 auto 的自动类型推断发生在编译期,所以使用 auto 并不会造成程序运行时效率的降低。 而是否会造成编译期的时间消耗,我认为是不会的,在未使用 auto 时,编译器也需要得知右操作数的类型,再与左操作数的类型进行比较,检查是否可以发生相应的转化,是否需要进行隐式类型转换。 auto 属于类类型推导 decltype 是包含声明修饰符的声明类型推导 int main() { int a = 10; auto val = a; cout << typeid(val).name() << endl; // i return 0; } 二、在定义函数模板时,用于声明依赖模板参数的变量类型 不到编译的时候,x * y 的真正类型很难确定 template <class _Tx,class _Ty> void Multiply(_Tx x, _Ty y) { auto v = x * y; std::cout << v; } int main() { int a = 10; double b = 1....

February 22, 2022 · 3 min · Rick Cui

gdb 调试使用

一、生成带有调试信息的程序 gcc main.c -o app -g 二、启动调试 启动调试:gdb app 设置参数: set args set args 123 abc 456 ddd 查看代码: list 或 l 查看代码显示行数:show listsize 设置代码显示行数:set listsize 20 查看当前文件: l l 行号 l 函数名 查看非当前文件: l 文件名:行号 l 文件名:函数名 三、断点相关 设置断点: break 行号 b 行号 b 函数名 b 文件名:行号 b 文件名:函数名 查看断点:info break 或 i b 删除断点: del 断点Num d Num d Num1 Num2 d Num1-Num10 设置断点无效:dis Num 断点生效:ena Num 设置条件断点:b 行号 if i == 10 四、调试相关 启动运行: start - s run - r 退出 gdb 调试:quit - q 打印变量的值:p 变量名 打印变量的类型:ptype 变量名 向下单步调试: next - n step - s 跳出函数体:finish 从循环中跳出(循环体中不能有断点):until 设置变量的值:set var 变量名 = value 继续运行到下一个断点:continue - c 监视变量:display 变量名 查看所有的监视变量的信息:info(i) display 取消变量监视:undisplay Num

February 20, 2022 · 1 min · Rick Cui