C++11新特性 变参模板、完美转发(简述)
变参模板 (Variadic Template) - 使得 emplace 可以接受任意参数,这样就可以适用于任意对象的构建
完美转发 - 使得接收下来的参数 能够原样的传递给对象的构造函数,这带来另一个方便性
先来看看下边的代码,看一下C11标准中提供的变参模板的用法之一:
1 #include <iostream> 2 #include <vector> 3 4 using namespace std; 5 6 class student 7 { 8 public: 9 student(int age, const char name[64]) 10 { 11 this -> age = age; 12 strncpy_s(this->name, sizeof(this->name) - 1, name, sizeof(name)); 13 cout << "调用有参构造函数" << endl; 14 } 15 16 student(const student& stud) 17 { 18 this->age = stud.age; 19 strncpy_s(this->name, sizeof(this->name) - 1, stud.name, sizeof(stud.name)); 20 cout << "调用拷贝构造函数" << endl; 21 } 22 23 ~student() 24 { 25 cout << "调用析构函数" << endl; 26 } 27 28 private: 29 int age; 30 char name[64]; 31 32 }; 33 34 int main() 35 { 36 vector<student> vectStud; 37 38 //方法一: 39 //student stud(18, "姓名"); 40 //vectStud.push_back(stud);
41 42 //方法二: 43 vectStud.push_back(student(18, "姓名")); 44 45 return 0; 46 }
方法一 与 方法二 的打印结果完全相同:
打印中第一行的有参构造函数是第39行代码调用到的,40行代码将这个对象放置到vector容器中时,会创建一个临时对象进行拷贝。这会造成效能的浪费。
为了解决这个问题C++11中加入了 变参模板 & 完美转发,使得 emplace 可以接受任意参数,这样就可以适用于任意对象的构建,如下代码:
1 #include <iostream> 2 #include <vector> 3 4 using namespace std; 5 6 class student 7 { 8 public: 9 student(int age, const char name[64]) 10 { 11 this -> age = age; 12 strncpy_s(this->name, sizeof(this->name) - 1, name, sizeof(name)); 13 cout << "调用有参构造函数" << endl; 14 } 15 16 student(const student& stud) 17 { 18 this->age = stud.age; 19 strncpy_s(this->name, sizeof(this->name) - 1, stud.name, sizeof(stud.name)); 20 cout << "调用拷贝构造函数" << endl; 21 } 22 23 ~student() 24 { 25 cout << "调用析构函数" << endl; 26 } 27 28 private: 29 int age; 30 char name[64]; 31 32 }; 33 34 int main() 35 { 36 vector<student> vectStud; 37 38 //方法一: 39 //student stud(18, "姓名"); 40 //vectStud.push_back(stud); 41 //方法二: 42 //vectStud.push_back(student(18, "姓名")); 43 44 //使用 emplace_back 45 vectStud.emplace_back(18, "姓名"); 46 47 return 0; 48 }
从打印结果可以看出,使用了 emplace_back 之后可以省略一个零时对象,并且不用进行拷贝构造函数的操作。
emplace_back 也就相当于一个 push_back 的操作
还有一个 emplace ,是在指定位置插入相关数据,类似于 insert,如下代码:
1 #include <iostream> 2 #include <vector> 3 4 using namespace std; 5 6 class student 7 { 8 public: 9 student(int age, const char name[64]) 10 { 11 this -> age = age; 12 strncpy_s(this->name, sizeof(this->name) - 1, name, sizeof(name)); 13 cout << "调用有参构造函数" << endl; 14 cout << "姓名:" << this->name << " " << "年龄:" << this->age << endl; 15 } 16 17 student(const student& stud) 18 { 19 this->age = stud.age; 20 strncpy_s(this->name, sizeof(this->name) - 1, stud.name, sizeof(stud.name)); 21 cout << "调用拷贝构造函数" << endl; 22 } 23 24 ~student() 25 { 26 cout << "调用析构函数" << endl; 27 } 28 29 private: 30 int age; 31 char name[64]; 32 }; 33 34 int main() 35 { 36 vector<student> vectStud; 37 38 //方法一: 39 //student stud(18, "姓名"); 40 //vectStud.push_back(stud); 41 //方法二: 42 //vectStud.push_back(student(18, "姓名")); 43 44 //使用 emplace_back 45 vectStud.emplace_back(18, "张三"); 46 47 //使用 emplace 48 vectStud.emplace(vectStud.begin(), 20, "李四"); 49 50 return 0; 51 }
打印结果:
这里有个有趣的现象,第5行出现了一个拷贝构造函数,并且调用了3次析构函数。
这是因为 vector 的存储方式决定的,因为 vector 的存储空间是由元素的多少进行变化的,而我们在第48行的代码中使用了 begin() ,在首位置插入,导致内存空间发生了变化。编译器需要将第一个张三拷贝至李四的前方,然后析构掉。如果前方在插入一个,将会把后两个元素再进行拷贝和析构,以此类推。
不同的编译器,对于 vector 新插入元素的拷贝和析构的顺序有可能不同,即使是 VS2015 与 VS2019 在拷贝和析构的顺序上也不相同。
========================================================================================================================