在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
C++ Standard之演化
C++ 2.0新特性包含语言和标准库两个层面,后者以header files的形式呈现。 #include <type_traits> #include <unordered_set> #include <forward_list> #include <array> #include <tuple> #include <regex> #include <thread> TR1版本中的很多特性是放在 确认编译器是否支持C++11编译器会定义一个 #include <iostream> using namespace std; int main() { cout << __cplusplus << endl; return 0; } C++11里一些重要的内容
Variadic Templates 在C语言的printf函数里面就有用到过 int printf ( const char * format, ... );
在C++2.0里
需要注意3处 // function 1 void printX() { } // function 2 template <typename T, typename... Types> void printX(const T& firstArg, const Types&... args) { cout << firstArg << endl; // print first argument printX(args...); // call printX() for remaining arguments } printX(7.5, "hello", bitset<16>(377), 42); //7.5 //hello //0000000101111001 //42
第二个函数里面的参数表示可以接受一个参数和一包参数,并且每个参数可以是不同数据类型的,可以用于做递归(将不定参数的数据一一分解)。 第一个函数(不带参数的)是必须的,因为最后会分解为一个参数和一个数量为零的包,后者调用不带参数的函数。 如果想知道这包参数(args)一共有多少个参数,则可以使用 1 // function 3 2 template <typename...Types> 3 void printX (const Types&... args) 4 { /*...*/ } 问题:函数2和函数3可以共存吗?若可,谁比较泛化?谁比较特化? 函数2和函数3都是数量不定的模板(variadic template),不同的是函数2接受一个和一包参数,函数3接受一包参数。两者并不会引起ambiguous,经过测试会调用函数2。 Space in Template Expressions
nullptr and std::nullptr_tC++11使用nullptr代替NULL(或0)表示空指针。 void f(int); void f(void*); f(0); // calls f(int) f(NULL); // calls f(int) if NULL is 0, ambiguous otherwise f(nullptr); // calls f(void*) 对于重载的f函数,按照之前NULL就是0的话,f(NULL)就会存在二义性。 Automatic Type Deduction with auto以前auto用来描述本地变量,因为运行结束之后本地变量就会销毁,所以local变量也叫作auto变量。 C++11里面用auto来定义变量或类,编译器会自动判断数据类型。 auto i = 42; // i has type int double f(); auto d = f(); // d has type double 编译器在编译的过程中知道什么东西是什么类型,也因此可以做模板的实参推导,所以在使用标准库中的算法时不需要指明每个参数的类型。auto主要用于过长或者过于复杂的数据类型,比如: 1 vector<string> v; 2 3 // ... 4 5 auto pos = v:begin(); // pos has type vector<string>::iterator 6 7 auto l = [](int x)->bool { // l has the type of a lambda 8 9 // ... // taking on int and returning a bool 10 11 } Uniform Initialization在C++11之前,程序员(特别是新手),很容易对初始化对象或变量时怎么写产生疑惑。初始化可能发生在小括号,大括号或者赋值运算符上。因此C++11引入一致性初始化(Uniform Initialization)的概念,任何初始化都可以使用共通的语法,即大括号。例如: int values[] {1, 2, 3}; vector<int> v {2, 3, 5, 7, 11, 13, 17}; vector<string> cities { "Berlin", "New York", "London", "Braunschweig", "Cairo", "Cologne" }; complex<double> c {4.0, 3.0} // equivalent to c(4.0,3.0) 所有初始化都可以通过大括号完成,是如何实现的呢? 实际上,编译器看到 以上面的代码为例, Initializer ListsInitializer Lists也跟Variadic Templates一样可以用于不定个数的元素。 当使用大括号时,若未定义具体的值,则会初始化为0值,若数据类型是指针,则初始化为nullptr。
若声明的数据类型和大括号内的数据类型不匹配时,会自动转换类型,但缩窄范围(如把double数据赋值给int)的转换是不允许的。 int x1(5.3); // ok, but x1 becomes 5 int x2 = 5.3 // ok, but x2 becomes 5 int x3{5.3}; // error, narrowing int x4 = {5.3} // error, narrowing char c1{7}; // ok, even though 7 is an int, this is not narrowing char c2{9999}; // error, narrowing (if 9999 doesn't fit into a char) std::vector<int> v1 {1, 2, 4, 5} // ok std::vector<int> v2 {1, 2.3, 4, 5.6} // error, narrowing Initializer Lists的背后通过
void print (std::initializer_list<int> vals) { for (auto p = vals.begin(); p != vals.end(); ++p) { std::cout << *p << std::endl; } } print({2,3,5,7,11,13,17}); // pass a list of values to print() 传给 当同时有特定个数的参数和initializer list作为参数时,会优先选用后者,测试代码如下: class P { public: // ctor version 1 P(int a, int b) { cout << "P(int, int), a=" << a << ", b=" << b << endl; } // ctor version 2 P(initializer_list<int> initlist) { cout << "P(initializer_list<int>), values= "; for (auto i : initlist) cout << i << ' '; cout << endl; } }; P p(77,5); // P(int, int), a=77, b=5 P q {77,5}; // P(initializer_list<int>), values= 77 5 P r {77,5,42}; // P(initializer_list<int>), values= 77 5 42 P s = { 77, 5 }; // P(initializer_list<int>), values= 77 5 如果没有initializer list的版本(版本2),q和s将会调用版本1的构造函数,而r会报错。 Rvalue references右值引用是C++03出现的一种新的引用类型,用于解决不必要的copy。当赋值的右边(拷贝来源端)是一个右值(rvalue),那么左边(拷贝接受端)的对象可以不重新分配内存,而直接“偷”右边对象的内容(这种情形只有在指针的情况下才会发生)。
// 以int试验 int a = 9; int b = 4; a = b; // ok b = a; // ok a = a+b; // ok a + b = 42; // Error: lvalue required as left operand of assignment // 以string试验 string s1("Hello"); string s2("World"); s1 + s2 = s2; // ok, s1+s2可以当做lvalue cout << "s1: " << s1 << endl; // s1: Hello cout << "s2: " << s2 << endl; // s2: World string() = "World"; // ok, 竟然可以对临时对象赋值 // 以complex试验 complex<int> c1(3,8), c2(1,0); c1 + c2 = complex<int>(4,9); // ok, c1+c2可以当做lvalue cout << "c1: " << c1 << endl; // c1:(3,8) cout << "c2: " << c2 << endl; // c2:(1,0) complex<int>() = complex<int>(4,9); // 竟然可以对临时对象赋值 函数的返回值是rvalue,对rvalue取地址是不允许的。 int foo() { return 5; } int x = foo(); // ok int* p = &foo(); // Error: 不能对rvalue取其地址 foo() = 7; // Error 对于临时对象(rvalue),可以通过右值引用(&&)避免重新分配内存创建对象;而对于lvalue,可以通过调用move()函数实现,但必须确保这个对象之后不会再被使用。 Perfect Forwarding所谓完美的传递,指的是在多级函数调用的过程中,参数的属性(可变的还是const的,lvalue还是rvalue)也能够被传递。 void process(int& i) { cout << "process(int&):" << i << endl; } void process(int&& i) { cout << "process(int&&):" << i << endl; } void forward(int&& i) { cout << "forward(int&&):" << i << ", "; process(i); } int a = 0; process(a); // process(int&):0 // 视为lvalue处理 process(1); // process(int&&):1 // 临时对象视为rvalue处理 process(move(a)); // process(int&&):0 // 强制将lvalue改为rvalue forward(2); // forward(int&&):2, process(int&):2 // rvalue经由forward()传给另一个函数后却变为lvalue // (原因是在传递过程中把它变为了非临时对象) forward(move(a)); // forward(int&&):0, process(int&):0 //! forward(a); // Error: cannot bind 'int' lvalue to 'int&&' const int &b = 1; //! process(b); // Error: no matching function for call 'process(const int&) //! process(move(b)); // Error: no matching function for call to // 'process(std::remove_rederence<const int&>::type)' //! int& x(5); // Error: invaild initialization of non-const reference of // tyoe 'int&' from an rvalue of type 'int' 显然目前的写法并不能实现属性传递。所以标准库里面做move的操作之前会调用forward(),借用下面这段代码解决
// ...\4.9.2\include\c++\bits\move.h // @brief Forward an lvalue. // @return The parameter cast to the specified type. // This function is used to implement "perfect forwarding". template<typename _Tp> constexpr _Tp&& forward(typename std::remove_reference<_Tp>::type& __t) noexcept { return static_cast<_Tp&&>(__t); } // @brief Forward an rvalue. // @return The parameter cast to the specified type. // This function is used to implement "perfect forwarding". template<typename _Tp> constexpr _Tp&& forward(typename std::remove_reference<_Tp>::type&& __t) noexcept { static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument" " substituting _Tp is an lvalue reference type"); return static_cast<_Tp&&>(__t); } // @brief Convert a value to an rvalue. // @param __t A thing of arbitrary type. // @return The parameter cast to an rvalue-reference to allow moving it. template<typename _Tp> constexpr typename std::remove_reference<_Tp>::type&& move(_Tp&& __t) noexcept { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); } 设计一个带有move的class1 class MyString 2 { 3 private: 4 char* _data; 5 size_t _len; 6 void _init_data(const char *s) 7 { 8 _data = new char[_len+1]; 9 memcpy(_data, s, _len); 10 _data[_len] = '\0'; 11 } 12 public: 13 // default ctor 14 MyString() : _data(NULL), _len(0) { } 15 // ctor 16 MyString(const char* p) : _len(strlen(p)) 17 { 18 _init_data(p); 19 } 20 // copy ctor 21 MyString(const MyString& str) : _len(str._len) 22 { 23 cout << "Copy Constructor is called! source: " << str._data 24 << " [" << (void*)(str._data) << ']' << endl; 25 _init_data(str._data); // COPY 26 } 27 // move ctor, with "noexcept" 28 MyString(MyString&& str) noexcept : _data(str._data), _len(str._len) { 29 cout << "Move Constructor is called! source: " << str._data 30 << " [" << (void*)(str._data) << ']' << endl; 31 str._len = 0; 32 str._data = NULL; // 这句很重要!!! 33 // 另外需要避免 delete (in dtor),不然会把临时对象杀掉 34 } 35 36 // copy assignment 37 MyString& operator=(const MyString& str) 38 { 39 cout << "Copy Assignment is called! source: " << str._data 40 << " [" << (void*)(str._data) << ']' << endl; 41 if (this != &str) { 42 if (_data) delete _data; 43 _len = str._len; 44 _init_data(str._data); // COPY! 45 } 46 else { 47 cout << "Self Assignment, Nothing to do." << endl; 48 } 49 return *this; 50 } 51 // move assignment 52 MyString& operator=(MyString&& str) noexcept 53 { 54 // 注意 noexcept 55 cout << "Move Assignment is called! source: " << str._data 56 << " [" << (void*)(str._data) << ']' << endl; 57 if (this != &str) 58 { 59 if (_data) delete _data; 60 _len = str._len; 61 _data = str._data; // MOVE! 62 str._len = 0; 63 str._data = NULL; // 这句很重要!!! 64 // 另外需要避免 delete (in dtor),不然会把临时对象杀掉 65 } 66 return *this; 67 } 68 69 // dtor 70 virtual ~MyString() 71 { 72 cout << "Destructor is called! " << "source: "; 73 if (_data) 74 cout << _data; 75 cout << " [" << (void*)(_data) << ']' << endl; 76 77 if (_data) 78 { 79 delete _data; 80 } 81 } 82 };
|
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论