C++11新特性(二)(语言keywords)

alias template

#include <vector>

using namespace std;

// 不可以对alias template做偏特化或全特化
template<typename T>
using Vec=vector<T, MyAlloc<T>>;  // alias template using新用法
//# define Vec<T> template<typename T> vector<T, MyAlloc<T>> // 使用宏不行

int main() {
    Vec<int> col;
// 如果使用宏上述会被替换为template<typename int> vector<int, allocator<int>>  error 不是我们想要的
// typedef vector<int, allocator<int>> Vec;   // typedef也无法做到,没法指定模板参数

    return 0;
}

可以使用模板别名接受参数,上述代码将vector<T, allocator< T >>使用Vec别名,如果想要替换自己的分配器Myalloc,让Vec接受模板参数T。

template template parameter

引出:有没有template语法能够在模板接受一个template参数Container时,当Container本身又是一个class template,能取出Container的template参数?例如收到一vector< string>,能够取出其元素类型string?

第一种方法:函数模板+iterator+traits

template<typename Container>
void test_moveable(Container c)
{
    typedef typename iterator_traits<typename Container::iterator>::value_type Valtype;
    
    for(long i=0; i<SIZE; ++i)
        c.insert(c.end(), Valtype());
    
    output_static_data(*(c.begin()));
    Container<T> c1(c);
    Container<T> c2(std::move(c));
    c1.swap(c2);
}

这里还涉及到了typename的第二种用法,用于表示后面跟着的是一个类型,主要是为了避免歧义,因为作用域符号后面跟着的可能是类型,也可能是成员。
这样做是可以达到效果的,但是却改变了函数签名,使用的时候我们需要这样调用:test_moveable(list()),和我们开始设计的是不一样的。
那么,有没有template语法能够在模板接受一个template参数Container时,当Container本身又是一个class template,能取出Container的template参数?例如收到一个vector< string>,能够取出其元素类型string?那么这就引出了模板模板参数的概念。

template <typename T,
          template <typename T>
              class Container
         >
class XCls
{
private:
    Container<T> c;
public:
    XCLs()
    {
        for(long i=0; i<SIZE; ++i)
            c.insert(c.end(), T());
 
        output_static_data(T());
        Container<T> c1(c);
        Container<T> c2(std::move(c));
        c1.swap(c2);
    }
};

使用上面的定义,调用时会出错

XCls<MyString, vector> c1;        //[Error] vector的实际类型和模板中的Container<T>类型不匹配

在模板中使用模板作为其参数时,由于编译器无法自动为容器推导其参数T,在需要用alias template来声明其容器的默认参数list<T, allocator<T>>

//不得在function body之内声明
template<typename T>
using Vec = vector<T, allocator<T>>;
 
XCls<MyString, Vec> c1;

typealias

类型别名,相当于typedef
(1)用于函数指针

using func = void(*)(int, int);
//相当于
typedef void (*func)(int, int);

说明fuc是一个函数类型
(2)在泛型编程中

template<typename T>
struct Container{
    using value_type = T;    //typedef T value_type;
};

声明了value_type是一个成员类型

template<typename Cntr>
void fn2(const Cntr& c)
{
    typename Cntr::value_type n;
}

constexpr

定义

值不会改变且在编译过程就能得到结果的常量表达式(字面值,用常量表达式初始化的对象)[string,自定义类不属于字面值]

指针

constexpr指针的初始值必须是nullptr0固定地址中的对象

只在编译时获得

const int m = get_val();//错误,m的值在运行时获得

对比

  • const需要认为验证赋给const对象初始值是不是常量表达式
  • constexpr可以有编译器验证是不是常量表达式
  • constexpr生命的指针限定符只对指针有效,与指向对象无关
const int *p1 = nullptr; //p1是指向常量的指针
constexpr int *p2 = nullptr //p2是指向整数的常量指针

constexpr函数

能用于常量表达式的函数,返回值和形参只能是字面值类型。

constexpr int foo(int i)
{
    return i+10;
}

int main()
{
    int i = 10;
    std::array<int, foo(2)> arr; // OK

    foo(i); // Call is Ok
    constexpr int m = foo(i);//错误,返回值不是常量
    std::array<int, foo(i)> arr1; // error: the value of ‘i’ is not usable in a constant expression

}

m报错不是常量表达式,调用array时也报错不是foo(i)返回值不是常量表达式,而在输入参数为常量2时不报错,为常量表达式。
注意:在调用过程中,constexpr函数的调用被替换成为结果值
如果传入的参数在编译期间被计算出来,如foo(2)则会被替换为结果值,如果传入的参数不能在编译时期计算出来如foo(i),则此函数与普通函数一样,不能作为常量表达式

nullptr

C++11定义了nullptr为指向NULL的void *指针,替代了以前用NULL=0指代空指针。
消除了原来的歧义:若NULL不为0,为一个整数值,则无法判断f(NULL);指向的是void *函数还是int函数。

类型

空指针类型的定义为typedef decltype(nullptr) nullptr_t

消除模板表达中的空格

vector<list<int> > //ok in each C++ version
vector<list<int>> //在C++11以后开始允许

auto类型推断

迭代器的源码推断

inline auto
operator-(const reverse_iterator<_Iterator> &__x,
          const reverse_iterator<_Iterator> &__y)
-> decltype(__x.base() - __y.base())
{ return __y.base() - __x.base(); }

返回值用auto替代,用->decltype()指明返回值类型

一致初始化{ }

所有初始化都可以用{ },编译器看到{t1, t2…tn}便会做出一个initializer_list< T >,关联到一个arrat< T, n>。调用函数(ctor构造函数)时该array内的元素可被编译器分解逐一传给函数。每个容器都有这个ctor。(若有函数参数为initializer_list< T >,则他们会整个调入函数。
vector<string> cities {"Berlin", "Beijing", "London"};后面的大括号形成一个initializer_list< string >,背后有个array<string, 3>。调用vector< string > ctors时找到了一个接收initializer_list< string>的vector< string> ctor。
第二种情况:
complex<double> c {4.0, 3.0>;调用complex< double> ctor时该array内的两个元素被分解传给ctor,因为complex中没有任何ctor(构造函数)接收initializer_list< double>。

initializer_list<>

  • 若类的构造函数中没有传入initializer_list参数的版本,则会被拆分为一个个元素穿进构造函数,但如果initializer_list中的元素个数多于构造函数中的参数个数时,则会出错。
  • 接收initializer_list的构造函数在类中为私有构造函数,由编译器调用。具体:编译器看到{}后,先创建出一个array<>,在创建出相应的initializer_list<>
  • 这个类调用构造函数时,只是把array的iterator和size传进来,并不是将其定义为成员。其中迭代器指向array首元素,长度就是array的长度。(initializer_list对象在拷贝的时候只是浅拷贝,拷贝指针,两个指针指向同一个array)
    initializer_list部分源码
 private:
        iterator        _M_array;
        size_type       _M_len;
        
        //编译器会在看到{}后,创造出一个array,再调用这个私有构造函数
        constexpr initializer_list(const_iterator __a, size_type __l)
            :_M_array(__a), _M_len(__l) { }

array部分源码

template<typename _Tp, std::size_t _Nm>
    struct array
    {
        typedef _Tp             value_type;
        typedef _Tp*            pointer;
        typedef value_type*     iterator;
 
        // Support for zero-sized arrays mandatory
        value_type _M_instance[_Nm ? _Nm : 1];
        
        iterator begin()
        { return iterator(&_M_instance[0]); } 
        
        iterator end()
        { return iterator(&_M_instance[_Nm]); }
    };

initializer_list的应用

  • 所有容器都接收指定任意数量initializer_list对象的值用于构建或赋值或insert()或assign()
  • algorithm也可以接受任意参数的initializer_list对象

explicit

  • 用于禁止构造函数自动调用。
  • 禁止构造函数的隐式类型转换

C++11以前:

class Fraction1 {
public:
    // explicit-one-argument ctor 可以把别的东西转换为Fraction这种
    explicit Fraction1(int num, int den = 1) : m_numerator(num), m_denominator(den) {}

    Fraction1 operator+(const Fraction1 &f) {
        return Fraction1(this->m_numerator + f.m_numerator);
    }


    void getNumAndDen() {
        cout << m_numerator << " " << m_denominator << endl;
    }

private:
    int m_numerator;    // 分子
    int m_denominator;  // 分母
};

若在此时调用

Fraction1 f1(3,5);
Fraction1 f2 = f1+3;

则会结果失败,由于原来3会隐式转换为(3,1),但explicit会禁止这种隐式转换,提示操作数为int和Fraction不同类型。

C++11以后:

class P {
public:
    P(int a, int b) {
        cout << "P(int a,int b)\n";
    }

    P(int a, int b, int c) {
        cout << "non-explicit P(int a,int b,int c)\n";
    }
    explicit P(int a, int b, int c,int d) {
        cout << "explicit P(int a,int b,int c,int d)\n";
    }
};

在此时调用P({1,2,3,4})则会出现报错,而调用P({1,2,3})则不会报错。报错内容为//error: converting to ‘P’ from initializer list would use explicit constructor ‘P::P(int, int, int, int)’不能将initializer_list对象强制转化为带explicit构造函数中的int参数。

=default, =delete

  • 可以在有构造函数情况写带=default的ctor,不可以在有构造拷贝函数的情况下写带=default或=delete的拷贝构造函数(赋值构造函数也是)
  • 一般函数没有=default,但有=delete
  • 析构函数不能=delete,否则无法删除对象
struct NoDtor
{
    NoDtor() = default;
    ~NoDtor() = delete; 
};
 
NoDtor nd;                   //error
NoDtor *p = new NoDtor();    //OK
delete p;                    //error

变量nd是声明在栈中的变量,在作用域结束后自动调用析构函数,调用析构函数由操作系统自动完成,因此编译器发现无法调用析构函数时就会报错。而new出来的对象是在堆上的,操作系统不会自动调用析构函数,需要手动销毁,只要不销毁就不会调用析构函数,也不会出错。

private copy

class PrivateCopy
{
private:
    PrivateCopy(const PrivateCopy&);
    PrivateCopy operator=(const PrivateCopy&);
public:
    PrivateCopy() = default;
    ~PrivateCopy();
};

这个class不能被普通的用户代码调用,但可以被friends和members调用。

noexcept

  • 在某条件成立时不丢异常void foo() noexcept(true){}
  • 如果异常一直没有处理,会最终调用std::terminate,然后调用std::abort
  • 在vector会二倍成长,需要移动的容器中使用&&的move function是必须声明noexcept,使编译器确信编写的move函数比原先的拷贝更快

override

在继承重写写错虚函数的参数类型时,让编译器抛出错误,而不是成为新函数。

decltype

让编译器找出一个表达式或对象的类型。例子:map<string, float> coll; decltype(coll)::value_type elem;

三种应用

声明返回值

template<typename T1, typename T2>
//decltype(x+y) add(T1 x, T2 y);错误,x和y变量没有声明,因为先后次序编译器不知道是什么
auto add(T1 x, T2 y) -> decltype(x+y)//暂定写不出来返回值类型,用auto的lambda表达式代替

元编程

map<string, float> coll;
    // 获取上述类型
decltype(coll)::value_type m{"as", 1};
//map<string, float>::value_type elem1;

通过对象获取容器的value_type

推断lambda的类型

只有lambda的对象,没有类型,用decltype去获取

auto cmp = [](const Person & p1, const Person & p2){...};
...
std::set<Person, decltype(cmp)> coll<cmp>;

final

  1. 修饰class,声明为最后的类,不能被继承
  2. 修饰virtual fuction,不能被复写

Lambdas

[ ]{...}();直接调用lambda或借助autoauto l = []{...}; l();
形式:[外部参数(值传递(默认 x)或引用传递(&x)或(=, &y)允许所有其他外部参数,y为引用)] (函数参数) mutable{ 函数体};
注意:
1.只在定义lambda时引入外部非静态对象作为参数,后面对外部参数的修改lambda不可见,调用时也只使用定义时引用的外部参数,可以理解为外部参数被拷贝到了函数类里面作为对象成员(在传引用时会受影响,且影响外界)
2.lambda类型为匿名函数对象
3.将Compare cmp的Lambda传入set的构造函数时,必须在模板中声明decltype(cmp)。同时必须在构造函数参数中加入cmp,否则编译器会因为无法调用Lambda的默认构造函数报错,因此用fuction object来定义排序准则会更直观

set部分源码如下

public:
    ......
    set() : t(Compare()) {}
    explicit set(const Compare& comp) : t(comp) {}
};

注意到,如果我们不把lambda对象作为参数传给set对象,就将会调用set的默认构造函数,而set的默认构造函数版本又将会调用传入的可调用类型的默认构造函数,但是lambda这种类型并没有默认构造函数和赋值运算符,所以编译器会报错(use of deleted function…);将lambda对象作为参数传给set对象,会调用set的另一个构造函数,这个构造函数可以正常使用。

小括号后面有三个可选的字段:

  • mutable表示是否可以修改捕获的值,如果值传递不写mutable不能在lambda中改变那个捕获的对象,如果是引用传递写不写mutable都能改
  • throwSpec表示抛出异常的情况
  • ->retType可以指出返回类型,一般配合decltype( )使用
    注意:这三个东西都是可选的,如果都不写,甚至可以省略前面的小括号,但只要有一个,前面的小括号就必须写,对于返回类型来说,如果不写,编译器会对返回值自动进行类型推导。

在定义排序准则时,Lambda和class出现如下转换
Lambda:在这里插入代码片[x, y](int n) {return x < n && n < y;}
class:

class LambdaFunctor{
public:
	LambdaFunctor(int a, int b): m_a(a), m_b(b) {}
	bool operator()(int n) const{
		return m_a < n && n < m_b;}
private:
	int m_a;
	int m_b;
}
LambdaFunctor(x,y);//这里调用与Lambda相同

可以看到Lambda节省了很多代码空间

容器array

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值