C++11新特性解析

   Table of Contents

类型推导

智能指针

移动语义

杂项

nullptr、0、NULL

constexpr


本篇博客对于C++11的新特性做一些详细的描述和记录。尽量用简洁的语言和小栗子说明C++11做了哪些优化工作。

类型推导

  • 模板函数类型推导。当调用一个模板函数时候,会自动进行模板参数推导。在推导的过程中,实参的引用性会被忽略(也就是说如果传进的是一个引用,则形参会当做非引用型处理),除此之外,如果是传值的话,实参的常量性也会被去除。
  • auto,是一个语法糖,其类型推导的方法与模板函数一致,唯一不同的是对于大括号初始化方法的推导,auto会推导为std::initializer_list类型,模板函数不会推导。除此之外,auto主要是提高了写代码时候的方便性,让程序员可以用这个语法糖来简化类型的书写(但是,其实你最好还是要清楚知道自己定义或函数返回值都是什么类型,然后可以用auto来替代显式类型声明)
  • decltype,主要的作用是在声明返回值型别依赖于形参型别的模板函数(和auto一起使用),其他地方的使用和auto差不多。在C++14中支持decltype(auto)这种使用方法。
template <typename T,typename U>
auto func(T a,U b) -> decltype(a+b)
{
    return a+b;
}

智能指针

    C++11中的智能指针常用的有三种unique_ptr,shared_ptr,weak_ptr(auto_ptr已经不考虑了)。智能指针主要是利用RAII的思想,将裸指针的管理用一个类的构造和析构来替代(但是和GC机制还是有很大的区别),下面每个分别进行阐述。

  • unique_ptr,这个用于管理专属所有权的资源,也就是说其不能够复制,只能通过移动进行资源传递,但是将unique_ptr指针转换成shared_ptr很方便。unique_ptr可以自己定义资源删除器,但是会增加对象的大小(原生的unique_ptr对象大小就是和裸指针一样,但是如果加上自定义的删除器,就会增加其大小)

       从使用来看,unique_ptr主要用于工厂模式的方法返回和Pimpl用法(Pointer to Implemetation ,在一个类中声明另一个impl类,对于impl类用unique_ptr进行管理,好处在于减少类之间的编译依赖关系)

  • shared_ptr,用于管理共享所有权的资源,其内部实现不仅有裸指针,还有一个int* count,指向引用计数的指针,所以其大小是裸指针的两倍。还有一点需要注意,shared_ptr要求线程安全,所以其引用计数的递增和递减操作都是原子性的。shared_ptr不可转换成unique_ptr。

        从使用来看,shared_ptr的适用范围相比如unique_ptr就广阔很多,但是值得注意的是,尽量不要用裸指针来初始化shared_ptr,因为可能发生一种情况,就是同一个裸指针初始化多个shared_ptr,这样就会对一个同样的资源有多个引用计数(控制块),当一个析构后,另一个就会是悬停空指针了,当其析构的时候会发生未定义行为解决方法有两个:①尽量用行如std::shared_ptr ptr(new A());②用make_shared替代裸指针初始化。首先make_shared会有性能的提升,其次,其在多参数构造的场景下,make_shared会保证无异常,裸指针可能由于参数传递构造顺序的问题产生异常。

#ifndef SHARED_PTR_H
#define SHARED_PTR_H
#include <iostream>
#include <atomic>
namespace haha_giraffe{

/*注意!!模板类不能声明实现分离,因为是在编译阶段模板具现化 */

template <typename T>
class Shared_ptr{
public:
    Shared_ptr():ptr(nullptr),count(new int(0)){
        
    }
    explicit Shared_ptr(T* value);     // Shared_ptr<int> ptr=new int(64); 加上explicit这个就无法成功,这个先是将内置指针转换成Shared_ptr
    ~Shared_ptr();
    Shared_ptr(const Shared_ptr<T>&);
    Shared_ptr<T>& operator = (const Shared_ptr<T>&);
    int usecount() const{
        return *count;
    }
    T* get() const{
        return ptr;
    }
    void reset(T* value) noexcept;
    void swap(Shared_ptr<T>&) noexcept;
private:
    T* ptr;
    //std::atomic<int*> count; 要不要将引用计数定义为原子变量保证shared_ptr的线程安全性
    int* count; //因为不同对象要共享这个count,而且不能定义成static
};

template <typename T>
Shared_ptr<T>::Shared_ptr(T* value)
    :ptr(value),
    count(new int(1))
{
}

template <typename T>
Shared_ptr<T>::~Shared_ptr(){
    (*count)--;
    if(*count==0){
        std::cout<<"destructor"<<std::endl;
        delete count;
        delete ptr;
    }
}

template <typename T>
Shared_ptr<T>::Shared_ptr(const Shared_ptr<T>& sha)
    :ptr(sha.ptr),
    count(sha.count)
{
    (*count)++;
}
    
template <typename T>
Shared_ptr<T>& Shared_ptr<T>::operator = (const Shared_ptr<T>& sha){
    if(this==&sha){
        return *this;
    }
    (*count)--;//原先的引用计数减一
    if((*count)==0){
        delete count;
        delete ptr;
    }
    count=sha.count;//更改计数
    ptr=sha.ptr;
    (*count)++;//计数加一
    return *this;
}

template <typename T>
void Shared_ptr<T>::reset(T* value) noexcept{
    (*count)--;
    ptr=value;
    count=new int(1);
}

template <typename T>
void Shared_ptr<T>::swap(Shared_ptr<T>& sptr) noexcept{
    int* tmpcount=count;
    auto aptr=ptr;
    ptr=sptr.ptr;
    count=sptr.count;
    sptr.ptr=aptr;
    sptr.count=tmpcount;
}

}
#endif
  • weak_ptr,这个智能指针的用处在于作为shared_ptr的一种扩充,其与shared_ptr的区别在于,其不会增加引用计数的个数,所以当引用计数为0的时候,指向相同管理资源的weak_ptr也一样会被析构。其主要的使用场景在于循环引用(类A中有指向类B的shared_ptr对象,同时类B中也有指向类A的shared_ptr对象,所以两个对象在析构的时候就会形成一个死锁的情况,这时就可以用weak_ptr来替代shared_ptr)

移动语义

    这一块是C++11的新特性,在这里分点进行叙述。

  1. 左值引用,右值引用,万能引用。首先要明白C++中什么左值和右值,参考左值与右值,而左值引用则形为T&,右值引用和万能引用都形为T&&,区别在于,万能引用的T型别是推导而来,一般用于模板函数和auto&&,一般情况下,左值引用只能用左值进行初始化,右值引用只能用右值进行初始化,而对于万能引用,如果是右值初始化则得到一个右值引用,如果是左值初始化则得到一个左值引用。
  2. std::move,std::forward和完美转发。std::move经常配和右值引用,其功能就是将左值变成右值,其本部本质上是一个强制类型转换。std::forward则配着万能引用使用,是一种有条件的强制类型转换,仅当实参是通过右值完成初始化时,他才会执行向右值的强制类型转换,其余都是执行向左值的强制类型转换。
  3. 引用折叠,当多个引用重叠在一起,就会发生引用折叠,规则是:如果任意引用为左值引用,则结果为左值引用,否则(即两个皆为右值引用),结果为右值引用。引用折叠一般出现在四种场景下:①模板实例化②auto变量的型别生成③生成和使用typedef和别名声明④decltype运用中(万能引用的实质=类型推导+引用折叠)

杂项

nullptr、0、NULL

    首先nullptr是在C++11中提出的一个关键字表示指针的字面常量,nullptr相比于0和NULL最大的区别在于,0和NULL不具备指针型别,当发生模板函数类型推导的时候,0和NULL可能会被推导为整型而不是指针类型,而nullptr则会被推导为nullptr_t。除此之外,当发生函数重载的时候可能会发生意外。

#include <cstddef>
#include <iostream>
 
template<class F, class A>
void Fwd(F f, A a)
{
    f(a);
}
 
void g(int* i)
{
    std::cout << "Function g called\n";
}
 
int main()
{
    g(NULL);           // 良好
    g(0);              // 良好
 
    Fwd(g, nullptr);   // 良好
//  Fwd(g, NULL);  // 错误:不存在函数 g(int)
}

    最后提一下,因为编译器对于0和NULL的类型推导不对,所以0和NULL不能用作空指针实现完美转发。

constexpr

    对于constexpr修饰的变量,其有两个特点:①具有常量性(和const一致,不可更改)②在编译期就已经得到变量的值(即只能通过编译期常量进行初始化,在编译期就可以算出来,这就是其与const的区别)

    对于constexpr修饰的函数,如果传入的参数能够在编译期计算出来,那么这个函数就会产生编译期的值,相反如果传入的参数不能在编译期计算出来,那么constexpr修饰的函数就和普通函数一样了,满足这样条件的函数,就可以用constexpr修饰。

    检查变量是否为constexpr,可以利用std::array只能传入编译期常量的长度值的特性。

参考书籍

     Effective Modern C++

     C++ Primer

相关博客

    https://zh.cppreference.com/w/cpp/language/nullptr

    https://www.zhihu.com/question/35614219

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值