C++ 实验 5.12

程序:变参模板输出多种类型参数  主要涉及右值引用,移动语义 可参考:右值引用....等等

先上代码:

#include <iostream>
#include <string>
#include <chrono>

struct St {
    std::string description;
};

template <class T>
void s_printf(T&& value) {
  std::cout << value << std::endl;
}
void s_printf(St& v) {
  std::cout << v.description << std::endl;
}
template<typename T, typename... Args>
auto s_printf(T&& value, Args&&... args) {
  s_printf(value);
  s_printf(args...);
}
int main() {
    St st {"Learn by Doing"};

    auto t1 = std::chrono::high_resolution_clock::now();

    st_printf(1, 2, 3.14, 5, 6, 7, 8, 9, 10, 11, 12, 13, "hello world", std::move(st));

    auto t2 = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::microseconds>(t2 - t1).count();
    std::cout << duration << std::endl;

    return 0;
}

auto t1 t2为了测试之间所包含代码执行的时间。

上面

 st_printf(1, 2, 3.14, 5, 6, 7, 8, 9, 10, 11, 12, 13, "hello world", std::move(st));

中 1 2  3.14 .... 13 这些都是字面值常量,是右值;(判断右值左值:能否对其使用取地址&,一大堆博文这么写/自己查)

而“hello world”是字符串字面值,类型为const char[] ,可以对其&取地址,所以是左值。

而&&在实参推导的时候,图说函数模板右值引用参数(T&amp;&amp;)类型推导规则(C++11)

不想打开连接的直接看总结,我把总结摘过来:

只要我们传递一个基本类型是A的左值,那么,传递后,T的类型就是A&,形参在函数体中的类型就是A&。

只要我们传递一个基本类型是A的右值,那么,传递后,T的类型就是A,形参在函数体中的类型就是A&&。

另外,模板参数类型推导是保留cv限定符(cv-qualifier,const和volatile限定符的统称)的,具体例子见《完美转发和标准库forward函数》。

①这里指形参在函数体中的实际类型

②函数返回的不具名左值引用依旧是左值,例如,上面代码中,Get(3)=5;是可以的。

③具名的右值引用是左值,不具名的右值引用是右值。

④这里,“基本类型是A”意思是:A,A&,A&&及A类型的字面值的基本类型都是A

所以“hello world”即便是左值,也可以把它传递给模板中右值引用的“形参”,这句话不严谨 仅供理解。

关于std::move

我们都知道move接受任意类型,并在内部通过static_cast<...>将其转换为&&

所以以下代码是可以正确运行的:

struct St {
    std::string description;
};
void s_printf(St&& v) {//如果换成& 则不能运行 因为传入的是右值引用
  std::cout << v.description << std::endl;
}
int main() {
    st_printf(std::move(st));}

那么为什么一开始的程序是&?

我们来看

 st_printf(1, 2, 3.14, 5, 6, 7, 8, 9, 10, 11, 12, 13, "hello world", std::move(st));

这是多个参数的,所以他会匹配

template<typename T, typename... Args>
auto s_printf(T&& value, Args&&... args) {
  s_printf(value);
  s_printf(args...);
}

这个时候,有一条规则不得不说了

右值引用和左值引用

  ①右值引用和左值引用都是属于引用类型。无论是声明一个左值引用还是右值引用,都必须立即进行初始化

  ②左值引用是具名变量/对象的别名,右值引用是匿名变量/对象的别名。

  ③左值和右值是独立于它的类型的,即左右值与类型没有直接关系,它们是表达式的属性具名的右值引用是左值,匿名的右值引用是右值。如Type&& t中t是个具名变量(最简单的表达式),t的类型是右值引用类型,但具有左值属性。而Type&& func()中的返回值(是个表达式)是右值引用类型,但具有右值属性(因为是个匿名对象)。

即:具名的右值引用是左值(左右值只表示表达式的属性,与表达式的类型没有关系)

所谓匿名对象也就是临时对象,自然也就是xvalue 将亡值--右值

这个时候使用

s_printf(args...);

传到

void s_printf(St& v) {
  std::cout << v.description << std::endl;
}

这个时候的arg是右值引用类型,但是他是左值,所以只能使用&,如果使用&& 那么就会匹配不到

当然了,移动语义在类的移动构造函数上有更大的意义。这个程序主要为了理解左值右值、左值引用右值引用。

利用重载<<来解决自定义类型的输出

std::ostream& operator<<(std::ostream& o, St &s)
{
    o<<s.description;return o;
}
auto s_printf(){}
template<typename T, typename... Args>
auto s_printf(T &&value, Args&&... args) {
std::cout<<value<<std::endl;
s_printf(std::forward<Args>(args)...);
}

不定义template的“特化”版本(不严谨,仅仅为了表达意思),转而重载<<,利用重载匹配来解决st类型的输出。

std::forward()  将一个或多个实参连同类型不变地转发给其他函数。它与move不同 必须通过显式模板实参来调用

forward<T>的返回类型是T&&  通过其返回类型上的引用折叠,forward可以保持给定实参的左值、右值类型。

为什么定义一个空的s_printf()

还没搞明白

难道最后forward会返回一个空值?也不对啊?



猜你喜欢

转载自blog.csdn.net/qq_33890670/article/details/80330417