> 文章列表 > 每天一点C++——杂记230408

每天一点C++——杂记230408

每天一点C++——杂记230408

右值引用

先介绍一下什么是左值,什么是右值。
左值是表达是结束后仍然存在的持久化对象,可取地址,右值是表达式结束后就不再存在的临时对象,不可取地址。所有的具名变量活对象都是左值,而右值不具名。

C++通过右值引用可以充分使用临时变量或者即将不使用的变量即右值资源,减少不必要的拷贝来提高效率。C++引入了右值引用&&,移动构造函数,移动赋值和std::move。

std::move

move主要实现右值引用去引用左值的情况。
move并没有移动什么,只是将传入的值转换为右值引用,相当于进行了一次类型转换。将一个左值引用强制转化为右值引用,继而可以通过右值引用使用该值,以用于移动语义。

原理

通过右值引用模板,利用引用折叠原理右值引用还是右值引用,左值的T&&转换为普通的左值引用,通过static cast进行强制类型转换返回T&&的右值引用,通过remove_refrence获取具体的类型。

优点

  • 将右值转换为左值,避免拷贝构造
  • 将对象的所有权转移,没有内存的搬迁和拷贝

移动构造

通过std::move实现,通过移动构造就对象的资源所有权都会转移给新对象,对旧对象的操作可能会出现未定义的行为。

拷贝构造,拷贝赋值,移动构造,移动赋值

注意:

  • 如果构造函数中涉及到内存的申请需要重写拷贝构造函数。
  • 拷贝构造的参数是引用,否则会出现循环嵌套调用,因为参数传递会默认调用拷贝构造函数
class Human {
public:Human() = defalut; //默认构造Human(Human &h): name(h.name), age(h.age){ //拷贝构造函数 cout << "copy construct ..." << std::endl;} Human(Human &&h): name(h.name), age(h.age){ //移动构造h.age = nullptr;} Human& opereatr=(Human &h) {// 拷贝赋值if (&h != this) {name = h.name;age = h.age;}return *this;}Human& opereatr=(Human &&h) { //移动赋值name = h.name;age = h.age;h.age = nullptr;return *this;}
private:string name;int *age;

泛型

泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础。

函数模板

函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化和显式实例化。
隐式实例化:让编译器根据实参推演模板参数的实际类型。
显式实例化:在函数名后的<>中指定模板参数的实际类型。

函数模板定义:

template <typename T1, typename T2>
T1 sum(T1 x, T2 y) {return static_cast<T1>(x + y);
}
int a = 1;
float b = 2.0;
c = sum(a, b); //隐式实例化
c = sum<int, float>(a, b); //显式实例化

类模板

类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。
类模板与函数模板区别主要有两点:

  • 类模板没有自动类型推导的使用方式
  • 类模板在模板参数列表中可以有默认参数

典型的例子可以参考vector

template<class T>
class vector{void push_back(T &data) {} 
}

类模板与继承

当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型
如果不指定,编译器无法给子类分配内存
如果想灵活指定出父类中T的类型,子类也需为类模板