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不会调用构造函数转换成复数。

image

 

range-based for statement 

image

  编译器在操作的时候,就是将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) 

image

不能对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。 

image

 

image

   lambda表达式的type是一种匿名函数对象(仿函数),对于每个lambda表达式都有唯一的type与之对应。如果想定义这个type的一个对象,则需要用到模板(templates)或者auto关键词。如果确实需要知道具体是什么type,可以使用decltype()。

image

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 完成。

image

例1:使用variadic templates处理不定类型不定数量的参数

image

// 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就可以了,以下是标准库中的做法:

image

#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函数,用于检测一堆数据中最大值(二)

 

image

// 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…()获取元素的个数。

image

#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:用于递归继承

  递归调用处理的都是参数,所以使用函数模板;递归继承处理的都是类型,所以使用类模板。

 

image

#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

#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;
}

 

https://en.cppreference.com/w/cpp/keyword

posted on 2018-12-01 00:14  flysong  阅读(805)  评论(0编辑  收藏  举报

导航