C++ 新标准 11/14 语言新特性 - 2
explicit关键字
C++2.0之前explicit用于一个实参的构造函数(none explicit one argument constructor)。多于一个参数的不会发生隐式转换。
1 struct Complex1 2 { 3 int real, imag; 4 5 Complex1(int re, int im=0) : real(re), imag(im) 6 { } 7 Complex1 operator+(const Complex1& x) 8 { 9 return Complex1((real + x.real) 10 , (imag + x.imag)); 11 } 12 }; 13 14 struct Complex2 15 { 16 int real, imag; 17 explicit 18 Complex2(int re, int im=0) : real(re), imag(im) 19 { } 20 Complex2 operator+(const Complex2& x) 21 { 22 return Complex2((real + x.real) 23 , (imag + x.imag)); 24 } 25 }; 26 27 Complex1 c1(12,5); 28 Complex1 c2 = c1 + 5; 29 30 Complex2 c21(12,5); 31 Complex2 c22 = c21 + 5;
Complex1的im有默认值,所以只有一个需要的实参,还是属于none explicit one argument constructor。编译器会去找是否可以将5转换为复数(5+0i),所以利用构造函数将5变为复数。Complex2的explicit的意思是当明确调用构造函数的时候才会调用它,故5不会调用构造函数转换成复数。
range-based for statement
编译器在操作的时候,就是将coll容器中的数据一个个取出来,放到左边的decl(一个声明的变量)中,每拿出一个元素,执行statement的操作。示例代码:
for (int i : {2, 3, 5, 7, 9, 13, 17, 19} ) { cout << i << endl; } vector<double> vec; for (auto elem : vec) { cout << elem << endl; } for (auto& elem : vec) { elem *= 3; }
当容器里的数据类型和decl的数据类型不匹配的时候,编译器会尝试去做类型转换,但如果像下面这样不允许转换,则会出现编译错误
class C { public: explicit C (const string& s); // explicit(!) type conversion from strings }; vector<string> vs; for (const C& elem : vs) { // ERROR: no conversion from string to C cout << elem << endl; }
=default, =delete
class Zoo { public: Zoo(int i1, int i2): d1(i1), d2(i2) { } Zoo(const Zoo&)=delete; // 拷贝构造 Zoo(Zoo&&)=default; // Move Constructor,C++11新增的右值引用 Zoo& operator=(const Zoo&)=default; // 拷贝赋值 Zoo& operator=(const Zoo&&)=delete; // Move assignment virtual ~Zoo() { } private: int d1, d2; };
default ctor不需要任何参数,且是一个空函数。看起来什么也没做,但比如是一个继承自父类的子类,子类的ctor必须调用父类的ctor,这时候默认的ctor就有了价值,虽然看起来是空的,但编译器在别的地方有一些隐藏的动作,调用父类的ctor。
构造函数、拷贝构造函数、拷贝赋值函数、析构函数,编译器都会生成默认的版本(假如程序员没有自行定义的话)。且这些函数都是public、inline的。
这些函数做了些什么呢?
default ctor和dtor主要给编译器一个地方用来放置隐藏在幕后的code,像是调用base classed以及non-static members的ctors和dtors。编译器生成的dtor是non-virtual的,除非这个class的base class本身声明有virtual dtor。至于copy ctor和copy assignment operator,编译器只是单纯将source object的每一个non-static data members拷贝到destination object。
#include <iostream> using namespace std; class Foo { public: Foo(int i): _i(i) { } Foo()=default; Foo(const Foo& x):_i(x._i) { } //! Foo(const Foo&)=default; // Error: 'Foo::Foo(const Foo&)'cannot be overloaded //! Foo(const Foo&)=delete; // Error: 'Foo::Foo(const Foo&)'cannot be overloaded Foo& operator=(const Foo& x) { _i=x._i; return *this;} //! Foo& opreator=(const Foo&)=default; // Error: 'Foo& opreator=(const Foo&)' cannot be overloaded //! Foo& opreator=(const Foo&)=delete; // Error: 'Foo& opreator=(const Foo&)' cannot be overloaded //! void func1()=default; // Error: 'void Foo::func1()' cannot be defaulted void func2()=delete; // ok //! ~Foo()=delete; // 这会造成使用Foo obj时出错->Error:use of deleted function 'Foo::~Foo()' ~Foo()=default; private: int _i; }; int main() { cout << __cplusplus << endl; Foo f1(5); Foo f2; // 如果没有写出=default版本->Error: no matching function for call to 'Foo::Foo()' Foo f3(f1); // 如果没有copy ctor=delete;->Error: use of deleted function 'Foo::Foo(const Foo&)' f3 = f2; // 如果没有copy assign=delete;->Error: use of deleted function 'Foo& Foo::operator=(const Foo&)' return 0; }
=default
用于Big-Five之外无意义,编译报错。=delete
可用于任何函数身上(=0只能用于virtual函数)。
Alias Template(template typedef)
不能对Alias Template进行偏特化或特化
Type Alias
// type alias, identical to // typedef void (*func)(int, int); using func = void(*)(int, int); // the name 'func' now denotes a pointer to function: void example(int, int) { } func fn = example;
// type alias can introduce a member typedef name template<typename T> struct Container { using value_type = T; // 等价于typedef T value_type }; // which can be used in generic programming template<typename Cntr> void fn2(const Cntr& c) { typename Cntr::value_type n; }
// type alias used to hide a template parameter template <class CharT> using mystring = std::basic_string<CharT, std::char_traits<CharT>>; mystring<char> str; // 标准库的<string>和<string_fwd.h>都有以下typedef,作用和上面的写法是一样的 typedef basic_string<char> string;
typedef和type alias这两种声明方式并没有不同。这种声明可以写在块作用域(block scope)、类作用域(class scope)或是命名空间作用域(namespace scope)。
using的用法总结
- using-directives(
using namespace std
) for namespaces and using-declarations(using std::cout
) for namespace members. - using-declarations for class members.
- type alias and alias template declaration. (since C++11)
noexcept
在函数名后面跟上noexcept表示程序员肯定这个函数不会抛出异常。如果一个函数有异常没有被处理的话,就会向调用它的函数抛异常,如果都没有处理异常的话,程序就会终止(通过调用std::terminate(),这个函数默认调用std::abort()把程序结束掉)。
void foo() noexcept;
noexcept后面还可以跟上一个(),里面写上条件(结果是bool的表达式),意思为当满足这个条件时,不会抛出异常。如果不写的话,相当于(true),所以上面的代码相当于下面这样写:
void foo() noexcept(true);
()内也可以下面这样的表达式:
void swap(Type& x, Type& y) noexcept (noexcept(x.swap(y))) { x.swap(y); }
意思是当x.swap(y)不抛出异常的时候swap函数也不会抛出异常。
必须使用noexcept的情况
当类同时有拷贝构造和搬移构造的时候,会优先使用搬移构造(move constructor)。如果是一些特殊的类,比如vector,必须在搬移构造和搬移析构上加上noexcept,因为在vector二倍增长的时候,会调用搬移构造函数,如果不加noexcept,则vector无法使用它。
You need to inform C++ (specifically std::vector) that your move constructor and destructor does not throw, using noexcept. Then the move constructor will be called when the vector grows.
If the constructor is not noexcept, std::vector can’t use it, since then it can’t ensure the exception guarantees demanded by the standard.—— StackOverflow how to enforce move semantics when a vector grows
growable conatiners(会发生memory reallocation)的只有两种:vector和deque。
override
C++11新增一个关键字override,所谓override,函数签名必须完全相同。若本意是要override,但写错了,导致违背原本意图,比如下面这样:
struct Base { virtual void vfunc(float) { } }; struct Derived1 : Base { virtual void vfunc(int) { } // accidentally create a new virtual function, when one intended to override a base class function // This is a common problem, particularly when a user goes to modify the base class. }; struct Derived2 : Base { virtual void vfunc(int) override { } // Error 'virtual void Derived2::vfunc(int)' marked override, but does not override virtual void vfunc(float) override { } };
override的意思是告诉编译器去检查这个类的父类(或父类的父类等)是否有一个具有相同函数签名的virtual函数,如果没有,则编译器会报错。
final
用于修饰类或者虚函数。
struct Base1 final { }; struct Derived1 : Base { }; // Error: cannot derive from 'final' base 'Base1' in derived type 'Derived1' struct Base2 { virtual void f() final; }; struct Derived2 : Base2 { void f(); // Error: overriding final function 'virtual void Base2::f()' };
decltype
使用decltype可以让编译器找出一个表达式结果的类型,这个很像是对于typeof的需求(GUN C当中有typeof,但是不是C++标准里的东西)。
map<string,float> coll; decltype(coll)::value_type elem; // 在C++11之前,无法通过对象取得type,而是必须确切知道它的类型,所以只能写成这样 map<string,float>::value_type elem;
By using the new decltype keyword, you can let the compiler find out the type of an expression.This is the realization of often requested typeof feature.
One application of decltype is ①to declare return types. Another is to use it ②in metaprogramming or to ③pass the type of a lambda.—— Nicolai M.Josuttis the C++ Standard Library 2/e
① decltype, used to declare return types
标准库里面的add()函数,加的两样东西都是一样的,比如int+int,但下面这种情况就不一样了。
template<typename T1, typename T2>
decltype(x+y) add(T1 x, T2 y); // 由于x和y未知,所以编译通不过
x和y可以是两个不同的类型,比如青菜加萝卜。能不能相加在于程序员对T1和T2的设计。在C++11之前因为不知道T1和T2是什么,所以不知道怎么设置返回类型,有了decltype就可以了(当然这只是让编译可以通过,如果x和y真的不能相加,调用它的代码编译时还是会报错)。而上面的代码,由于编译器是顺序编译的,看到x和y的时候不知道它们是什么,所以编译还是通不过。需要用下面的写法:
template<typename T1, typename T2>
auto add(T1 x, T2 y)->decltype(x+y);
② decltype, used in metaprogramming
template<typename T> void test(T obj) { // 当我们手上有type,可取其inner typedef,没问题 map<string,float>::value_type elem1; // 面对obj取其class type的inner typedef,因为如今我们有了工具decltype map<string,float> coll; decltype(coll)::value_type elem2; // 有了decltype也可以这样写 typedef typename decltype(obj)::iterator iType; // typedef typename T::iterator iType; decltype(obj) anotherOby(obj); }
如果使用者使用的时候,指定进入的是复数test(complex<int>())
,由于复数没有迭代器,上面的代码还是会报错。所以,模板只是半成品,还需要看调用它的代码。
③ decltype, used to pass the type of a lambda
auto cmp = [](const Person& p1, const Person& p2) { return p1.lastname()<p2.lastname() || (p1.lastname()==p2.lastname() && p1.firstname()<p2.firstname()); }; std::set<Person,delctype(cmp)> coll<cmp>;
面对lambda,我们手上往往只有object,没有type,要获得其type就得借助于decltype。
lambdas
C++11引入了lambdas,它允许程序员定义出inline function,可以作为参数或者本地变量使用。lambdas改变了程序员对C++标准库的使用方式。我们在使用C++标准库的时候,往往会写一些小东西来声明我们的意图,比如sort时怎么比大小就可以由程序员来定义,通过函数对象(仿函数)来实现,也可以用过lambda来写。
[] { std::cout << "hello lambda" << std::endl; } //看起来是一个函数,其实是一个对象,所以不会有输出。可以写成下面这样 [] { std::cout << "hello lambda" << std::endl; } (); // prints "hello lambda" 小括号不是产生临时对象的意思,而是当做函数执行 auto l = [] { std::cout << "hello lambda" << std::endl; }; l(); // prints "hello lambda"
lambdas表达式的完整形式
[…](…)mutableopt throwSpecopt -> retTypeopt {…}
- []为lambda introducer,即lambda导入器(导入符号),说明这是一个lambda表达式。里面可以放lambda表达式需要取用(捕获)的外部变量。可以有值捕获和引用捕获两种方式。
- opt表示mutable、throwSpec、retType是可选的(可写可不写),但如果出现其中任一的话,必须写上前面的小括号指定参数。如果三个都没有,小括号可写可不写(视是否有参数而定)。
- retType用于描述返回值类型,由于lambda表达式写法特殊,由中括号开始,所以没有办法在前面指定返回类型。所以换作这种方式。
- 对于值捕获的变量,lambda不会改变其值,如果希望改变其值,需要加上mutable。
lambda表达式的type是一种匿名函数对象(仿函数),对于每个lambda表达式都有唯一的type与之对应。如果想定义这个type的一个对象,则需要用到模板(templates)或者auto关键词。如果确实需要知道具体是什么type,可以使用decltype()。
auto cmp = [](const Person& p1, const Person& p2) { return p1.lastname()<p2.lastname()|| (p1.lastname()==p2.lastname()&& p1.firstname()<p2.firstname()); }; std::set<Person,decltype(cmp)>coll(cmp);
Variadic Templates
从C++11开始,模板可以接受数量不定的参数,即variadic templates。
模板参数变化:
参数个数——利用参数个数逐一递减的特性,实现递归函数调用,使用function template 完成。
参数类型——利用参数个数逐一递减导致参数类型也逐一递减,实现递归继承或者递归复合,以class template 完成。
例1:使用variadic templates处理不定类型不定数量的参数
// 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 } // function 3 template <typename... Types> void printX(const Types&... args) { } //函数2比较特化,所以函数2和函数3可以共存,且函数3永远不会被调用。
例2:使用variadic templates重写printf()
#include <iostream> // http://stackoverflow.com/questions/3634379/variadic-templates void printX(const char* s) { while (*s) { if (*s=='%' && *(++s)!='%') throw std::runtime_error("invaild format string: missing arguments"); std::cout << *s++; } } template<typename T, typename... Args> void printX(const char* s, T value, Args... args) { while (*s) { if (*s=='%' && *(++s)!='%') { std::cout << value; printX(++s, args...); // call even when *s==0 to detect extra arguments return; } std::cout << *s++; } } int main() { int* pi = new int; printX("%d %s %p %f\n", 15, "This is Ace.", pi, 3.14159); // 15 This is Ace. 0x329ee0 3.14159 return 0; }
例3:设计max函数,用于检测一堆数据中最大的那个(一)
如果参数types皆同,则无数动用variadic templates,只需要使用initializer_list就可以了,以下是标准库中的做法:
#include <iostream> #include <algorithm> int main() { std::cout << std::max({57, 48, 60, 100, 20, 18}) << std::endl; // 为了配合后面max函数接收的是initializer_list,所以需要给initializer_list的参数 return 0; }
例4:设计max函数,用于检测一堆数据中最大值(二)
// http://stackoverflow.com/questions/3634379/variadic-templates int maximum(int n) { return n; } template<typename... Args> int maximum(int n, Args... args) { return std::max(n, maximum(args...));//不断调用std::max()而完成最大值的获取 }
例5:类模板,使用不同方式处理first和last元素
cout << make_tuple(7.5, string("hello"), bitset<16>(377), 42);
// 需要实现的打印效果 [7.5,hello,0000000101111001,42]
要进行这样的处理,必须知道处理的元素有几个,所以需要使用sizeof…()获取元素的个数。
#include <iostream> #include <algorithm> #include <bitset> #include <tuple> using namespace std; // boost: util/printtuple.hpp // helper: print element with index IDX of tuple with MAX elements template<int IDX, int MAX, typename... Args> struct PRINT_TUPLE { static void print(ostream& os, const tuple<Args...>& t) { os << get<IDX>(t) << (IDX+1==MAX ? "" : ","); PRINT_TUPLE<IDX+1, MAX, Args...>::print(os, t); } }; // partial specialization to end the recursion template<int MAX, typename... Args> struct PRINT_TUPLE<MAX, MAX, Args...> { static void print (std::ostream& os, const tuple<Args...>& t) { } }; // output operator for tuples template <typename... Args> ostream& operator<<(ostream& os, const tuple<Args...>& t) { os << "["; PRINT_TUPLE<0, sizeof...(Args), Args...>::print(os, t); return os << "]"; } int main() { cout << make_tuple(7.5, string("hello"), bitset<16>(377), 42); // 需要实现的打印效果 [7.5,hello,0000000101111001,42] return 0; }
例6:用于递归继承
递归调用处理的都是参数,所以使用函数模板;递归继承处理的都是类型,所以使用类模板。
#include <iostream> using namespace std; template<typename... Values> class tupleX; template<> class tupleX<> { }; // 将数据分为一个和一包,一个用于声明变量,一包再做成一个tupleX,用于继承 template<typename Head, typename... Tail> class tupleX<Head, Tail...> :private tupleX<Tail...> // 使用私有继承,这样做只是为了满足递归继承,并不是is-a的关系 { typedef tupleX<Tail...> inherited; protected: Head m_head; public: tupleX() { } tupleX(Head v, Tail... vtail) : m_head(v), inherited(vtail...) { } // typename Head::type head() { return m_head; } // [Error] no type named 'type' in 'class std::basic_string<char>' // 上面这样写会报错,因为像int、float并不知道Head::type是什么,所以可以用decltype // auto head()->decltype(m_head) { return m_head; } // 但其实可以直接用Head,因为返回类型就是Head Head head() { return m_head; } inherited& tail() { return *this; } // 强转后得到的就是tail的部分 }; int main() { string a = "nico"; cout << sizeof("nico") << endl; //5 cout << sizeof(a) << endl; //32 float b = 6.3; cout << sizeof(6.3) << endl; //8 cout << sizeof(b) << endl; //4 cout << sizeof(41) << endl; //4 tupleX<int, float, string> t(41, 6.3, "nico"); cout << sizeof(t) << endl; //40 cout << t.head() << endl; //41 cout << t.tail().head() << endl;//6.3 cout << t.tail().tail().head() << endl; //nico return 0; }
例7:用于递归组合![image image](https://img2018.cnblogs.com/blog/1293190/201812/1293190-20181201151644686-1610037180.png)
#include <iostream> using namespace std; template<typename... Values> class tup; template<> class tup<> { }; template<typename Head, typename... Tail> class tup<Head, Tail...> { typedef tup<Tail...> composited; protected: composited m_tail; Head m_head; public: tup() { } tup(Head v, Tail... vtail) : m_tail(vtail...), m_head(v) { } Head head() { return m_head; } composited& tail() { return m_tail; } // 这里需要用引用,不然修改值时因为改的是拷贝版本,原始版本不会被改变 }; int main() { string a = "nico"; cout << sizeof("nico") << endl; //5 cout << sizeof(a) << endl; //32 float b = 6.3; cout << sizeof(6.3) << endl; //8 cout << sizeof(b) << endl; //4 cout << sizeof(41) << endl; //4 tup<int, float, string> t(41, 6.3, "nico"); cout << sizeof(t) << endl; //56 cout << t.head() << endl; //41 cout << t.tail().head() << endl;//6.3 cout << t.tail().tail().head() << endl; //nico tup<string> t1("nico"); tup<float, string> t2(6.3, "nico"); cout << sizeof({tup<>()}) << endl; //1 cout << sizeof(t1) << endl; //40 cout << sizeof(t2) << endl; //48 return 0; }