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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章