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指针的初始值必须是nullptr
或0
或固定地址中的对象
只在编译时获得
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
- 修饰class,声明为最后的类,不能被继承
- 修饰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节省了很多代码空间