C++11常用特性學習-保證穩定(_func_/long long/static_assert/final/override)

C++11常用特性學習

C++11標準爲C++編程語言的第三個官方標準,正式名叫ISO/IEC 14882:2011 - Information technology – Programming languages – C++。 在正式標準發佈前,原名C++0x。2014年8月18日,經過C++標準委員投票,C++14標準獲得一致通過。目前C++17已經發布指導文件,增加了許多語言特性,進一步完善C++語言支持和特性(作死的道路越走越遠,規範的API,統一類庫和編譯特性都木有啊);
學習要了解的有:

  1. 新的算法和類庫;
  2. auto和decltype類型推導的關係;
  3. 移動語義,以及右值引用是如何解決轉發問題;
  4. default/delete函數以及override是怎麼回事;
  5. 異常描述符和noexcept;
  6. 原子類型和新的內存模型;
  7. C++11並行編程;

主要參考:《深入理解C++11新特性解析與應用》以及隨筆分類 - C++11學習每個C++開發者都應該使用的十個C++11特性開發者都應該使用的10個C++11特性C++11新特性等博客。本文只學習C11常用並且在VS,GCC和Clang等編譯器都已經較好的支持並且常用和易於理解的部分功能。

編譯器的選擇

C11準於2011年發佈並出版,出版時,主流編譯器並不支持C11的所有特性,目前(2015年11月)公司主流項目使用的編譯器VS12 (Visual Studio 2012)、g++ 4.7和Clang 3.1對C++的支持力度各部相同;但對常用的auto等常用功能都有了支持,具體支持情況見:C++11各編譯器支持情況對比;目前VS13(Visual Studio 2013)、gcc 4.8.1已經支持C11大部分特性。VS15(Visual Studio 2015)支持 C++11/14/17 功能(現代 C++)GCC 5.2 已經開始支持C17功能。可以選用Vs2013和gcc4.8.1來學習。(後記:MD,VS2013 C11預覽版BUG滿天飛,VS15版安裝包5個G,搞死人啊!!)

C++之父Bjame Stroustrup說C11就像一個新語言,的確,C11核心已經發生了巨大的變化,它現在支持Lambda表達式,對象類型自動推斷,統一的初始化語法,委託構造函數,deleted和defaulted函數聲明nullptr,以及最重要的右值引用。C11中值得關注的幾大變化:

◆ Lambda表達式
◆自動類型推斷和decltype
◆統一初始化語法
◆ Deleted和Defaulted函數
◆ nullptr
◆ 委託構造函數
◆ 右值引用

上述特性在文章:C++11它就像一個新語言掀起C++ 11的神祕面紗提供的連接中有詳細的探討。相對於C++98/03來說,主要是爲了增強和優化如下幾點:
◆通過內存模型、線程、原子操作等支持本地並行編程(Native Concurrentcy);
◆通過統一初始化表達式、auto、declytype、移動語義等來統一對泛型編程的支持;
◆通過constexpr、POD(概念)等更好支持系統編程;
◆通過內聯命名空間、繼承構造函數和右值引用等、以更好的支持庫的構建。

注:通過以上辛苦的工作,C11朝着“恐怖編程語言泛型”前進;

保證穩定和兼容的新特性

_func_預定義標識符

func預定義標識符表示函數的名字;

#include <string>
#include <iostream>
using namespace std;
const char* hello() { return __func__; }
const char* world() { return __func__; }

int main(){
    cout << hello() << ", " << world() << endl; // hello, world
}

我們定義了兩個函數hello和world。利用func預定義標識符,我們返回了函數的名字,並將其打印出來。事實上,按照標準定義,編譯器會隱式地在函數的定義之後定義func標識符。
void FuncFail( string func_name = func) {};// 無法通過編譯
這是由於在參數聲明時,func還未被定義。

long long整型

long long整型有兩種:long long和unsigned long long。在C++11中,標準要求long long整型可以在不同平臺上有不同的長度,但至少有64位。我們在寫常數字面量時,可以使用LL後綴(或是ll)標識一個long long類型的字面量,而ULL(或ull、Ull、uLL)表示一個unsigned long long類型的字面量。比如:
long long int lli = -9000000000000000000LL;
unsigned long long int ulli = -9000000000000000000ULL;
與long long整型相關的一共有3個:LLONG_MIN、LLONG_MAX和ULLONG_MIN,它們分別代表了平臺上最小的long long值、最大的long long值,以及最大的unsigned long long值。

#include <climits>
#include <cstdio>
using namespace std;
int main() {
    long long ll_min = LLONG_MIN;
    long long ll_max = LLONG_MAX;
    unsigned long long ull_max = ULLONG_MAX;
    printf("min of long long: %lld\n", ll_min); // min of long long: -9223372036854775808
    printf("max of long long: %lld\n", ll_max); // max of long long: 9223372036854775807
    printf("max of unsigned long long: %llu\n", ull_max);   // max of unsigned long long: 18446744073709551615
}

斷言:運行時與預處理時

在C++中,程序員也可以定義宏NDEBUG來禁用assert宏。

#include <cassert>
using namespace std;
// 一個簡單的堆內存數組分配函數
char * ArrayAlloc(int n) {
    assert(n > 0);  // 斷言,n必須大於0
    return new char [n];
}
int main (){
    char* a = ArrayAlloc(0);
} 

這對發佈程序來說還是必要的。因爲程序用戶對程序退出總是敏感的,而且部分的程序錯誤也未必會導致程序全部功能失效。那麼通過定義NDEBUG宏發佈程序就可以儘量避免程序退出的狀況。而當程序有問題時,通過沒有定義宏NDEBUG的版本,程序員則可以比較容易地找到出問題的位置。事實上,assert宏在中的實現方式類似於下列形式:

#ifdef  NDEBUG
# define assert(expr)           (static_cast<void> (0))
#else
...
#endif

可以看到,一旦定義了NDBUG宏,assert宏將被展開爲一條無意義的C語句(通常會被編譯器優化掉)。但是assert是在運行時進行斷言,我們希望在有些時候編譯的過程中就能發現某些問題,從而方便排錯。

斷言assert宏只有在程序運行時才能起作用。而#error只在編譯器預處理時才能起作用。有的時候,我們希望在編譯時能做一些斷言。在一些C++的模板的編寫中,我們希望對參數進行初步判斷,如保證兩種類型的長度一致,這樣bit_copy才能夠保證複製操作不會遇到越界等問題。這種情況下,正確產生斷言的時機應該在模板實例化時,即編譯時期。事實上,利用語言規則實現靜態斷言的討論非常多,比較典型的實現是開源庫Boost內置的BOOST_STATIC_ASSERT斷言機制(利用sizeof操作符)。
在C++11標準中,引入了static_assert斷言來解決這個問題。static_assert使用起來非常簡單,它接收兩個參數,一個是斷言表達式,這個表達式通常需要返回一個bool值;一個則是警告信息,它通常也就是一段字符串。如:

template <typename t, typename u> int bit_copy(t& a, u& b){
     static_assert(sizeof(b) == sizeof(a),"the parameters of bit_copy must have same width.");
};

這樣的錯誤信息就非常清楚,也非常有利於程序員排錯。而由於static_assert是編譯時期的斷言,其使用範圍不像assert一樣受到限制。

final/override控制

Java語言使用了final關鍵字來阻止函數繼續重寫。final關鍵字的作用是使派生類不可覆蓋它所修飾的虛函數。C11也採用了類似的做法,

struct Object{
    virtual void fun() = 0;
};
struct Base : public Object {
    void fun() final;   // 聲明爲final
};
struct Derived : public Base {
    void fun();     // 無法通過編譯
};
// 編譯選項:g++ -c -std=c++11 2-10-2.cpp

在C++11中爲了幫助程序員寫繼承結構複雜的類型,引入了虛函數描述符override,如果派生類在虛函數聲明時使用了override描述符,那麼該函數必須重載其基類中的同名函數,否則代碼將無法通過編譯。防止在子類重載時出現參數和拼寫錯誤。

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