C++ 14 Paper N3778C++ Sized Deallocation》導讀

C++ 14 Paper N3664《Clarifying Memory Allocation》導讀

C++ 14 Paper N3653《Member initializers and aggregates》導讀

C++ 14 Paper N3323《A Proposal to Tweak Certain C++ Contextual Conversions》導讀

C++ 14 Paper N3472《Binary Literals in the C++ Core Language》導讀

C++ 14 本身引入的特性就不多, 只是針對 C++ 11 的修補. 因此, 這篇文章之後, Jonny'Blog 將進入 C++ 14 時代, 所有程式碼至少是 C++ 14 標準

1. 《Member initializers and aggregates》導讀

這篇 Paper 只是針對標準進行了一定的修改. 首先, 在 C++ 11 中, 針對聚合類別的定義, 要求不能存在使用函式呼叫式或者指派式進行了預設初始化的非靜態成員變數. 但是在 C++ 14 中, 針對這個限制進行了移除

2. 《C++ Sized Deallocation》導讀

C++ 11 允許給一個類別多載一個帶有大小的 operator delete, 但是 C++ 11 又不允許多載全域名稱空間中帶有大小的 ::operator delete. 有時候, 這可能會導致效能低下. 因為當帶有大小的多載 operator delete 不可用的時候, 需要呼叫全域的 ::operator delete. 這個時候, 就需要主動去尋找需要回收的物件的大小

因此 C++ 14 有了

void operator delete(void * ptr, std::size_t sz) noexcept;

void operator delete[](void *ptr, std::size_t sz) noexcept;

3. 《Clarifying Memory Allocation》導讀

在 C++ 14 之前, 對於使用 new 進行記憶體的配置, 編碼器在處理這些程式碼的時候, 被 C++ 標準禁止優化. 例如

#include <vector>

using namespace std;
int main(int argc, char *argv[]) {
    for(auto i {0}; i < 128; ++i) {
        for(auto j {0}; j < 256; ++j) {
            vector<int> vec(j);
        }
    }
}

這段程式碼沒什麼實際用途, 因此優秀的編碼器會直接將其優化為

int main(int argc, char *argv[]) {
    //empty
}

這是因為 std::vector 的建構子包含了使用 operator new 的記憶體配置, 而 C++ 11 禁止對此進行優化. 某些編碼器編碼出來的程式, 其效能可能就會因此受到影響

但是這篇 Paper 提出了解除這個限制, 允許對記憶體配置的程式碼進行優化

4. 《A Proposal to Tweak Certain C++ Contextual Conversions》導讀

在 C++ 11 下, 某些型別轉換是不盡如人意的, 考慮下列程式碼 :

#include <type_traits>
#include <cassert>

template <typename T, typename = typename std::enable_if<std::is_arithmetic<T>::value or std::is_pointer<T>::value>::type>
class zero_init {
private:
    T value;
public:
    zero_init() : value {static_cast<T>(0)} {}
    zero_init(T value) : value {value} {}
    operator T() const {
        return this->value;
    }
    operator T &() {
        return this->value;
    }
};

這個類別的第二個樣板參數使用了 SFINAE, 具體大家可以參考《Template Meta Programming》這個系列的文章. 這裡我簡單解釋一下, 第二個樣板引數一般是不給出, 那麼當 T 是可計算的型別 (整型型別和浮點數型別) 或者指標型別的時候, 類別 zero_init 的物件才可以被宣告成功; 否則, 就會產生編碼錯誤. 也就是通過第二個樣板參數的預設引數, 限制了樣板可以接受的型別

上面所有的程式碼都非常平常, 因此我們再考慮下列程式碼 :

zero_init<int *> p;     //1
assert(p == 0);     //2
p = new int(42);        //3
assert(*p == 42);       //4
delete p;       //5
delete (p + 0);     //6
delete +p;      //7

我們暫時不考慮第六和第七行這兩行同時出現會導致的未定行為, 因為我們僅僅對語意進行分析. 對於第一行我們要分析的程式碼, 樣板引數為 int *, p 內部的 value0 初始化, 並且 value 的型別為 int *. 對於第二行來說, 由於類別 zero_init 並沒有多載的比較運算子, 因此需要對 p 進行轉型, 剛好 p 對應的型別 zero_init<int *> 有一個多載的轉型運算子 operator int *&(). 通過這個轉型運算子, 使得 p 可以和 0 進行比較. 對於第三行, 編碼器會為 zero_init<T> 產生一個預設的複製指派運算子, 因此通過建構子 zero_init(int *) 建構一個臨時物件, 然後指派給 p. 第四行的行為和第二行相同, 此處不再累贅. 但是第五行則會產生編碼錯誤, 因為使用多載的轉型運算子 operator T() constoperator T &() 都可以, 編碼器無法判斷哪一個更好. 第六行和第七行最終都是呼叫 operator int *&()

這篇 Paper 主要是解決了編碼錯誤那一行的限制問題. 在 C++ 14 中, 編碼器會優先呼叫 operator T &() 這個多載的運算子

5. 《Binary Literals in the C++ Core Language》導讀

這篇 Paper 主要是給 C++ 增加了二進制的字面值常數, 使用 0b 或者 0B 打頭表示二進制, 例如 0b10001101 表示字面值數字 141. 除此之外, Paper 還提出讓二進制字面值支援分隔符. Paper 中使用的是 "_", 但是通過測試, 我發現這種分隔符並不被編碼器支援