C++11深入学习知识点整理(一)

学习C++11过程中逐渐整理的知识点和引用链接,特粘出来供大家参考下。(知识点的整理是无规则的,还有些不是C++11的,也整合在一起了。毕竟能新学到几个知识点,也够本了。。)


[博客引用]

>

>

[pod类型]

>

[表达式的值类型]

>

>

>

表达式根据其值的类型可分为以下三类:

lvalue:左值,即传统意义上的左值。

xvalue(expiring value):x值,指通过“右值引用”产生的对象。这里x可以理解为即将消失(expiring),也可理解为中间(横跨左值和右值)。

prvalue(pure rvalue):纯右值,即传统意义上的右值。

两种复合类型:

glvalue(general lvalue):泛左值,由左值和x值构成。泛左值具有动态的类型和对象属性。

rvalue:右值,由x值和纯右值构成。右值具有潜在的可移动性。

 

C++11深入学习知识点整理(一)_第1张图片

 

[引用(左值和右值)和移动语义和完美转发]

>

>

>

>

$左值引用和右值引用和universal references(通用引用)

- 右值引用就是对一个右值进行引用的类型,因为右值不具名,因此只能通过引用的方式找到它

- 通过右值引用的声明,该右值又“重获新生”,其生命周期与右值引用类型变量的生命周期一样

- 无论声明为右值引用还是左值引用都必须立即初始化

- 只有当发生自动类型推断时(如函数模板的类型自动推导,或auto关键字),&&才是一个universal references。

$移动语义 (move semantic)和完美转发 (perfect forwarding)

 

C++11深入学习知识点整理(一)_第2张图片

#std::move和std::forward

>

[std::tuple&std::make_tuple]

>

std::tuple tp1("Sven Cheng", 77, 66.1);
std::string name;
int weight;
float f;
auto tp2 = std::make_tuple(std::ref(name), std::ref(weight), std::ref(f)) = tp1;

[emplace]

>

>

$vector

emplace <=>  insert

emplace_back​  <=> ​push_back

$set

emplcace <=>  insert

$map

emplace <=>  insert

 

[类的默认函数]

>

1 默认构造函数

2 默认拷贝构造函数

3 默认析构函数

4 默认重载赋值运算符函数

5 默认重载取址运算符函数

6 默认重载取址运算符const函数

7 默认移动构造函数(C++11)

8 默认重载移动赋值操作符函数(C++11)

[type_traits(类型萃取)]

>

>

>

$type_traits类型萃取

(1)type_traits通过定义一些结构体或类,并利用模板类特化和偏特化的能力,给类型赋予一些特性,这些特性根据类型的不同而异。在程序设计中可以使用这些traits来判断一个类型的一些特性,引发C++的函数重载机制,实现同一种操作因类型不同而异的效果。

(2)type_traits提供了丰富的编译期计算、查询、判断、转换和选择的帮助类。

(3)type_traits的类型选择功能,在一定程序上可以消除冗长的switch-cast或if-else的语句。提高代码可维护性。type_traits的类型判断功能,在编译期可以检查出是否是正确的类型,以能编写更安全的代码。

//STL中的is_const的完整实现
template
struct integral_constant
{   // convenient template for integral constant types
    static constexpr _Ty value = _Val;

    typedef _Ty value_type;
    typedef integral_constant<_Ty, _Val> type;

    constexpr operator value_type() const noexcept
    {   // return stored value
        return (value);
    }

    constexpr value_type operator()() const noexcept
    {   // return stored value
        return (value);
    }
};

typedef integral_constant true_type;
typedef integral_constant false_type;

template
struct is_const
    : false_type
{   // determine whether _Ty is const qualified
};

template
struct is_const
    : true_type
{   // determine whether _Ty is const qualified
};
int main() {

    static_assert(is_const::value,"error");//error
    static_assert(is_const::value, "error");//ok
    return 0;
}

[模板]

#数据类模板和模板类的特化

>

- 模板参数可以是数值型参数;

- 数值型模板参数必须在编译期间唯一确定;

- 数组类模板是基于数值型模板参数实现的;

- 数组类模板是简易的线性表数据结构;

//数值型模板
/* 用最高效的方法验证从 1 加到 n 的和;不用循环和等差数列求和公式 */
template< int N >
class Sum
{
public:
    static const int VALUE = Sum::VALUE + N;  // 递归定义
};
 
/* 定义上述模板类的特化实现,实现递归出口 */
template< >
class Sum < 1 >
{
public:
    static const int VALUE = 1;
};
//
//数组模板类
template 
class Array
{
    T a[N];  // 数组元素的类型和大小;
}
Array ad;  // 使用模板时,数值型参数必须是常量,不能是变量;

>

>

$模板的特化

- 类模板和函数模板都可以被全特化;

- 类模板能偏特化,不能被重载;

- 函数模板能重载,不能被偏特化。

$模板调用优先级从高到低

全特化模板>偏特化模板(重载模板)>主版本模板

这样的优先级顺序对性能也是最好的。

#可变参数模板

>

>

>

$初识参数包

- 模板参数包(以tuple为例):templateclass tuple

- 函数参数包:如template void f(T…args)

- 省略号“…”

01 当声明一个变量(或标识符)为可变参数时(即变量是一个形参),省略号位于该变量的左侧。

02 当使用参数包时(即变量作为实参),省略号位于参数名称的右侧,表示立即展开该参数,这个过程也被称为解包。

- 包扩展表达式“exp…”相当于将省略号左侧的参数包exp(可能是个变量或表达式)视为一个整体来进行扩展。

$展开参数包

- 可变模板参数不能直接作为变量保存起来,需要借助tuple保存起来。

[lambda]

>

Lambda 的语法形式如下:

[函数对象参数] (操作符重载函数参数) mutable 或 exception 声明 -> 返回值类型 {函数体}

 

$栈或堆中定义方式

auto my_lambda_func = [&](int x) { /* ... */ };

auto my_onheap_lambda_func = new auto([=](int x) { /* ... */ });

 

$lambda函数内部是如何工作的?

[异常(exception noexcecpt)]

$函数的异常说明

>

- 异常说明 进行对一个函数的异常进行说明, 如果函数抛出异常,被抛出的异常将是包含在该说明中的一种或是从列出的异常中派生的类型。

- 编译器不会对异常说明进行检测,异常说明更多的是写给函数的用户看。

$noexcecpt

>

该关键字告诉编译器,函数中不会发生异常,这有利于编译器对程序做更多的优化。

如果在运行时,noexecpt函数向外抛出了异常(如果函数内部捕捉了异常并完成处理,这种情况不算抛出异常),程序会直接终止,调用std::terminate()函数,该函数内部会调用std::abort()终止程序。

// 在新版本的编译器中,析构函数是默认加上关键字noexcept的,可以使用noexcept(false)去取消。下面代码可以检测编译器是否给析构函数加上关键字noexcept
struct X
{
    ~X() { };
};
int main()
{
    X x;
    static_assert(noexcept(x.~X()), "Ouch!");
}

[RAII]

>

$什么是RAII?

RAII(Resource Acquisition Is Initialization),也称直译为“资源获取就是初始化”,是C++语言的一种管理资源、避免泄漏的机制。

C++标准保证任何情况下,已构造的对象最终会销毁,即它的析构函数最终会被调用。

RAII 机制就是利用了C++的上述特性,在需要获取使用资源RES的时候,构造一个临时对象(T),在其构造T时获取资源,在T生命期控制对RES的访问使之始终保持有效,最后在T析构的时候释放资源。以达到安全管理资源对象,避免资源泄漏的目的。

 

[智能指针]

>

>

$性能

- 因为 C++的 zero cost abstraction 的特点,unique_ptr 在默认情况下和裸指针的大小是一样的。所以内存上没有任何的额外消耗,性能是最优的。

- 因为 C++的 zero cost abstraction 的特点,unique_ptr 在默认情况下和裸指针的大小是一样的。所以内存上没有任何的额外消耗,性能是最优的。

内存占用高。 - shared_ptr 的内存占用是裸指针的两倍。因为除了要管理一个裸指针外,还要维护一个引用计数。因此相比于- unique_ptr, shared_ptr 的内存占用更高

原子操作性能低。 考虑到线程安全问题,引用计数的增减必须是原子操作。而原子操作一般情况下都比非原子操作慢。

使用移动优化性能。shared_ptr 在性能上固然是低于 unique_ptr。而通常情况,我们也可以尽量避免 shared_ptr 复制。如果,一个 shared_ptr 需要将所有权共享给另外一个新的 shared_ptr,而我们确定在之后的代码中都不再使用这个 shared_ptr

$weak_ptr的作用(避免智能指针互相引用,导致内存泄漏)

class Parent

{

public:

    shared_ptr child;

};

class Child

{

public:

    shared_ptr parent;

};

shared_ptr pA(new Parent);

shared_ptr pB(new Child);

pA->child = pB;

pB->parent = pA;

那么这是一个非常鲜明的移动语义。对于此种场景,我们尽量使用 std::move,将 shared_ptr 转移给新的对象。因为移动不用增加引用计数,因此性能比复制更好。

 

C++11深入学习知识点整理(一)_第3张图片

#shared_ptr线程安全性分析

>

>

#shared_from_this

我们往往会需要在类内部使用自身的 shared_ptr,例如:

 

class Widget

{

public:

 void do_something(A& a)

 {

 a.widget = 该对象的shared_ptr;

 }

}

我们需要把当前 shared_ptr 对象同时交由对象 a 进行管理。意味着,当前对象的生命周期的结束不能早于对象 a。因为对象 a 在析构之前还是有可能会使用到a.widget。

 

如果我们直接a.widget = this;, 那肯定不行, 因为这样并没有增加当前 shared_ptr 的引用计数。shared_ptr 还是有可能早于对象 a 释放。

 

如果我们使用a.widget = std::make_shared(this);,肯定也不行,因为这个新创建的 shared_ptr,跟当前对象的 shared_ptr 毫无关系。当前对象的 shared_ptr 生命周期结束后,依然会释放掉当前内存,那么之后a.widget依然是不合法的。

 

对于这种,需要在对象内部获取该对象自身的 shared_ptr, 那么该类必须继承std::enable_shared_from_this。代码如下:

 

class Widget : public std::enable_shared_from_this

{

public:

 void do_something(A& a)

 {

 a.widget = shared_from_this();

 }

}

这样才是合法的做法。

 

 

 

你可能感兴趣的:(C++,c++)