兼容性和穩(wěn)定性

C++11 與 C99 的兼容

C11 之前最新的 C標準是 1999 年制定的 C99 標準。而第一個 C++ 語言的標準卻是在 1998 年(C++98)痰催,隨后的 C++03 標準也只是對 C++98 進行了小的修正兜辞。這樣一來迎瞧,雖然C語言發(fā)展中大多數(shù)改進都被引入到了 C++ 的標準中,但是還是存在一些屬于 C99 的"漏網(wǎng)之魚".所以C++11將以下C99特性的支持也納入了新標準中:

  • C99 中預(yù)定義宏
  • __fun__ 預(yù)定義標識符
  • _Pragma 標識符
  • 不定參數(shù)宏定義以及 __VA_ARGS__
  • 寬窄字符串連接

預(yù)定義宏

宏名稱 功能描述
__STDC_HOSTED__ 如果編譯器的目標系統(tǒng)環(huán)境中包含完整的C庫逸吵,那么這個宏就定義為1
__STDC__ C編譯器常用這個宏的值來表示編譯器的實現(xiàn)是否和C一致凶硅。
__STDC_VERSION__ C編譯器常用這個宏的值來表示編譯器所支持的C標準的版本。比如:1999mmL
__STDC_ISO_10646__ 定義為一個 yyyymmL 格式的整數(shù)常量扫皱,比如 199712L足绅,用來表示C++ 編譯環(huán)境符合某個版本的 ISO/IEC 10646標準
#include <iostream>

int main()
{
    std::cout << "Standard Clib: " << __STDC_HOSTED__ << std::endl; // Standard Clib: 1
    std::cout << "Standard C: " << __STDC__ << std::endl; // Standard C:1
//    std::cout << "C Standard version: " << __STDC_VERSION__ << std::endl;
//    std::cout << "ISO/IEC " << __STDC_ISO_10646 << std::endl; // ISO/IEC 200009

    return 0;
}

__func__ 預(yù)定義標識符

#include <iostream>

const char* hello() { return __func__; }
const char* world() { return __func__; }

int main()
{
    std::cout << hello() << ", " << world() << std::endl; // hello, world
    return 0;
}
const char* hello() {
    static const char* __func__ = "hello";
    return __func__;
}
#include <iostream>

struct TestStruct
{
    TestStruct() : name(__func__) {}
    const char *name;
};
int main()
{
    TestStruct ts;
    std::cout << ts.name << std::endl;

    return 0;
}

__func__ 不可以做函數(shù)的默認參數(shù)

void FunFail(string func_name = __func__) {}; // error
// 由于在參數(shù)聲明時,__func__ 還未被定義

_Pragma 操作符

#pragrma 是一條編譯器預(yù)處理的指令(processor directive)韩脑。

#pragrma once

// 等同于
#ifndef THIS_HEADER
#define THIS_HEADER
// ...
#endif

c++11

_Pragma(字符串字面量)  // _Pragma 是一個操作符

_Pragma("once");

#define CONCAT(x) PRAGMA(concat on #x)
#define PRAGMA(x) _Pragma(#x)
CONCAT(../concat.dir)

// _Pragma(concat on "../concat.dir")
// #pragrma 不能在宏中展開

__VA_ARGS__

#define PR(...) printf(__VA_ARGS__)
#include <stdio.h>

#define LOG(...) {\
    fprintf(stderr, "%s: Line %d:\t", __FILE__, __LINE__); \
    fprintf(stderr, __VA_ARGS__); \
    fprintf(stderr, "\n"); \
}

int main()
{
    int x = 3;

    LOG("x = %d", x); // ../1/main.cpp: Line 13: x = 3

    return 0;
}

寬窄字符串的連接

之前氢妈,將 char 轉(zhuǎn)成 wchar_t 是未定義的行為。

C++11段多,將char 和 wchar_t 進行連接時允懂,編譯器會將 char 轉(zhuǎn)換成 wchar_t,然后進行連接.

long long 整型

C++11 相對于 C++98 在整型上最大的改變就是多了 long long衩匣。早在 1995 年,long long 就被提議寫入 C++98 標準粥航,但是 C++ 委員會拒絕了琅捏。后來,long long 卻進入了 C99 標準递雀,而且事實上也被很多編譯器支持柄延。

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 = -900000000000000000000ULL;

了解平臺上 long long 大小的方法: <climits>

#include <iostream>
#include <climits>

int main()
{
    std::cout << "[ " << LLONG_MIN << ", " << LLONG_MAX << " ]" << std::endl;  // [ -9223372036854775808, 9223372036854775807 ]
    std::cout << ULLONG_MAX << std::endl; // 18446744073709551615

    return 0;
}

擴展的整型

標準整型(standard interger type)

C++11 的 5 種標準的有符號整型

  • signed char
  • short int
  • int
  • long int
  • long long int

每種有符號整型都有對應(yīng)的無符號整型.并且和對應(yīng)整型的存儲空間一致。

擴展整型(extended integer type)

C++11 對擴展整型的規(guī)定:有符號和無符號對應(yīng)杨凑,且存儲空間一致滤奈。

宏 __cplusplus

#indef __cplusplus
extern "C" {
#endif
// ...
#ifndef __cplusplus
}
#endif

extern "C" 可以抑制 C++ 對函數(shù)名,變量名等符號(symbol)進行重命名(name mangling)撩满,因此編譯出的C目標文件和C++目標文件中的變量蜒程,函數(shù)名等符號都相同,鏈接器可以可靠地對兩種類型的目標文件進行鏈接伺帘。

鑒于以上做法昭躺,程序員可能認為 __cplusplus 只有 “被定義” 和 “未定義” 兩種狀態(tài)。事實卻非如此伪嫁,__cplusplus 這個宏常被定義為一個整型值领炫。隨著標準的變化,__cplusplus 宏一般會是一個比以往標準中更大的值张咳。

  • C++03 ——> __cplusplus == 199711L
  • C++11 ——> __cplusplus == 201103L
// 對 C++11 進行支持
#if __cplusplus < 201103L
#error "should use C++11 implementation"
#endif

靜態(tài)斷言

斷言:運行時與預(yù)處理時

#include <iostream>
#include <cassert>

char *ArrayAlloc(int n)
{
    assert(n > 0);
    return new char[n];
}

int main()
{
    char* a = ArrayAlloc(0);

    return 0;
}

在 C++ 中帝洪,可以使用 NDEBUG 來禁用 assert .

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

靜態(tài)斷言與 static_assert

assert 只能在程序運行時起作用似舵。

error 只能在編譯器預(yù)處理時起作用

#include <cassert>
using namespace std;

// 枚舉編譯器對各種特性的支持,每個枚舉值占一位
enum FeatureSupports {
    C99         =   0x0001,
    ExtInt      =   0x0002,
    SAssert     =   0x0004,
    NoExcept    =   0x0008,
    SMAX        =   0x0010,
};

// 一個編譯器類型,包括名稱碟狞,特性支持等
struct Compiler {
    const char * name;
    int spp;    // 使用FeatureSupports枚舉
};

int main() {
    // 檢查枚舉值是否完備
    assert((SMAX - 1) == (C99 | ExtInt | SAssert | NoExcept));
    /*
     * assert 時運行時斷言
    */

    Compiler a = {"abc", (C99 | SAssert)};
    // ...
    if (a.spp & C99) {
        // 一些代碼...
    }
}
#include <cassert>
#include <cstring>
using namespace std;

template <typename T, typename U> int bit_copy(T& a, U& b){
    assert(sizeof(b) == sizeof(a));
    memcpy(&a,&b,sizeof(b));
};

int main() {
    int a = 0x2468;
    double b;
    bit_copy(a, b);
}

總而言之啄枕,我們想在編譯時期就觸發(fā)斷言。

Boos 內(nèi)置的 BOOST_STATIC_ASSERT 靜態(tài)斷言機制是利用 sizeof()操作符族沃。

我們可以利用 “除0” 會導(dǎo)致編譯器報錯這個特性實現(xiàn)靜態(tài)斷言频祝。

#define assert_static(e) \
    do { \
        enum { assert_static__ = 1 / (e) }; \
    } while (0)
#include <cstring>
using namespace std;

#define assert_static(e) \
    do { \
        enum { assert_static__ = 1/(e) }; \
    } while (0)

template <typename T, typename U> int bit_copy(T& a, U& b){
    assert_static(sizeof(b) == sizeof(a));
    memcpy(&a,&b,sizeof(b));
};

int main() {
    int a = 0x2468;
    double b = 2.0;
    bit_copy(a, b);
}

缺點:診斷信息不充分準確。

C++11 的 靜態(tài)斷言 static_assert脆淹。static_assert 接收2個參數(shù)常空,一個是斷言表達式,這個表達式需要返回 bool 值盖溺;一個則是斷言警告信息漓糙,通常是一個字符串。

#include <cstring>
#include <cassert>
using namespace std;

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.");
    memcpy(&a,&b,sizeof(b));
};

int main() {
    int a = 0x2468;
    double b = 2.0;
    bit_copy(a, b);
}

static_assert 是編譯時期的斷言烘嘱。

static_assert(sizeof(int) == 8, "This 64-bit machine should follow this!");

int main() { return 0; }
  • static_assert 接收的必須是常量表達式.【編譯時期可以計算的表達式】
int positive(const int n) {
    static_assert(n > 0, "value must >0");
    /*
     * 涉及變量昆禽,應(yīng)該使用 assert
    */
}

noexcept 修飾符 和 noexcept 操作符

斷言是用于排除邏輯上不存在的狀態(tài)。

異常時用于排除邏輯上的錯誤蝇庭。

C++98 的異常處理:

void excpt_func() throw(int, double) { ... }

由于該特性很少被使用醉鳖,C++11中被棄用了。表示函數(shù)不會拋出異常的動態(tài)異常聲明 throw() 也被新的 noexcept 異常聲明所取代哮内。

noexcept 修飾函數(shù)盗棵,表示該函數(shù)不會拋出異常。不過與 throw() 動態(tài)異常聲明不同的是北发,C++11 中如果 noexcept 修飾的函數(shù)拋出了異常纹因,編譯器可以選擇直接調(diào)用 std::terminate() 函數(shù)來終止程序的運行,這比基于異常機制的 throw() 在效率上會高一些琳拨。這是因為異常機制會帶來一些開銷瞭恰,比如函數(shù)拋出異常,會導(dǎo)致函數(shù)棧被依次地展開(unwind),并依幀調(diào)用在本幀中已構(gòu)造的自動變量的析構(gòu)函數(shù)等狱庇。

void excpt_func() noexcept;

void excpt_func() noexcept(常量表達式);
/*
 * 常量表達式會被轉(zhuǎn)換成一個 bool 類型的值寄疏。
 * 如果為 ture,該函數(shù)不會拋出異常;反之僵井,可能拋出異常陕截。
*/
#include <iostream>
using namespace std;

void Throw() { throw 1; }

void NoBlockThrow() { Throw(); }

void BlockThrow() noexcept { Throw(); }

int main() {
    try {
        Throw();
    } catch(...) {
        cout << "Found throw." << endl;     // Found throw.
    }

    try {
        NoBlockThrow();
    } catch(...) {
        cout << "Throw is not blocked." << endl;    // Throw is not blocked.
    }

    try {
        BlockThrow();   // terminate called after throwing an instance of 'int'
    } catch(...) {
        cout << "Found throw 1." << endl;
    }
}
  • noexcept 作為一個操作符,通撑玻可以用于模板
template <class T>
void fun() noexcept(noexcept(T())) {

}

fun() 是否是一個 noexcept 函數(shù)农曲,由 T() 表達式是否拋出異常決定。這里,第二個 noexcept 就是一個 noexcept 操作符乳规。當其參數(shù)是一個有可能拋出異常的表達式的時候形葬,其返回值為 false,反之為 ture. 這樣一來暮的,我們就可以使模板函數(shù)根據(jù)條件實現(xiàn) noexcept 修飾的版本或無 noexcept 修飾的版本笙以。從泛型編程的角度來看,這樣的設(shè)計保證了關(guān)于“函數(shù)是否拋出異扯潮纾”這樣的問題可以通過表達式進行推導(dǎo)猖腕。因此這也可以視作 C++11 為了更好地支持泛型編程而引入的特性。

雖然 noexcept 修飾的函數(shù)通過 std::terminate 的調(diào)用來結(jié)束程序的執(zhí)行的方式可能會帶來很多問題恨闪,比如無法保證對象的析構(gòu)函數(shù)的正常調(diào)用倘感,無法保證棧空間的釋放等咙咽,但很多時候老玛,“暴力”地終止整個程序確實是很簡單有效的做法。事實上钧敞,noexcept 被廣泛地蜡豹,系統(tǒng)地應(yīng)用在 C++11 的標準庫中,用于提高標準庫的性能溉苛,以及滿足一些阻止異常擴散的需求余素。

// C++98
template<class T> class A {
public:
    static constexpr T min() throw() { return T(); }
    static constexpr T max() throw() { return T(); }
    static constexpr T lowest() throw() { return T(); }
};

// C++11
template<class T> class A {
public:
    static constexpr T min() noexcept { return T(); }
    static constexpr T max() noexcept { return T(); }
    static constexpr T lowest() noexcept { return T(); }
}
// C++98
void* operator new(std::size_t) throw(std::bad_alloc);
void* operator new[](std::size_t) throw(std::bad_alloc);

// C++11
void* operator new(std::size_t) noexcept(false);
void* operator new[](std::size_t) noexcept(false);

C++11 默認將 delete 函數(shù)設(shè)置成 noexcept,可以保證應(yīng)用程序的安全性炊昆。

void operator delete(void *) noexcept;
void operator delete[] (void *) noexcept;

C++11 默認讓類的析構(gòu)函數(shù)默認也是 noexcept(ture) 的。如果程序員顯式的為析構(gòu)函數(shù)制定了 noexcept 威根,或者讓類的基類成員有 noexcept(false) 的西溝函數(shù)凤巨,析構(gòu)函數(shù)就不會再保持默認值。

#include <iostream>
using namespace std;

struct A {
    ~A() { throw 1; }
};

struct B {
    ~B() noexcept(false) { throw 2; }
};

struct C {
    B b;
};

int funA() { A a; }
int funB() { B b; }
int funC() { C c; }


int main() {
    try {
        funB();
    } catch(...) {
        cout << "caught funB." << endl; // caught funB.
    }

    try {
        funC();
    } catch(...) {
        cout << "caught funC." << endl; // caught funC.
    }

    try {
        funA(); // terminate called after throwing an instance of 'int'
    } catch(...){
        cout << "caught funA." << endl;
    }
}

快速初始化成員變量

  • C++98:
    • 使用 ‘=’ 初始化類中成員變量洛搀,成員變量必須滿足:
      ① static ② const ③ 整型或枚舉型
class Init {
public:
    Init() : a(0) {}
    Init(int d) : a(d) {}

private:
    int a;
    const static int b = 0;  // ok
    int c = 1;               // error
    static int d = 0;        // error
    static const double e = 1.3;      // error,不是整型或枚舉型
    static const char *const f = "e"; // error, 不是整型或枚舉型
}
  • C++11
    • 允許非靜態(tài)成員變量的初始化敢茁,且有多種形式。
struct {
    int a = 1;         // 使用 '=' 初始化
    double e {2.3};    // 使用 '{}' 初始化
};
#include <string>

using namespace std;

struct C {
    C(int i) :
        c(i) {}

    int c;
};

struct Init {
    int a = 1;
    string b("Hello");  // error
    C c(1);             // error
}

圓括號表達式初始化非靜態(tài)成員 b 和 c 都會出錯留美。

  • C++11 支持就地初始化非靜態(tài)成員的同時彰檬,又支持初始化列表。如果兩者同時使用谎砾,是否會沖突逢倍?
#include <iostream>
using namespace std;

struct Mem {
    Mem() { cout << "Mem defulat, num = " << num << endl; }
    Mem(int i)
        : num(i) {
            cout << "Mem defulat, num = " << num << endl;
        }

    int num = 2; // 使用 = 初始化非靜態(tài)成員
};

class Group {
public:
    Group() { cout << "Group default. val: " << val << endl; }
    Group(int i)
        : val('G'),
          a(i) {
              cout <<"Group. val: " << val << endl;
          }
    void NumOfA() { cout << "number of A: " << a.num << endl; }
    void NumOfB() { cout << "number of B: " << b.num << endl; }

private:
    char val{'g'}; // 使用 {} 初始化非靜態(tài)成員
    Mem a;
    Mem b{19};     // 使用 {} 初始化非靜態(tài)成員
};

int main() {
    Mem member;  // Mem defulat, num = 2
    Group group; // Mem default, num = 2
                 // Mem default, num = 19
                 // Group default. val: g

    group.NumOfA();  // number of A: 2
    group.NumOfB();  // number of B: 19

    Group group2(7); // Mem defulat, num = 7
                     // Mem defulat, num = 19
                     // Group. val: G

    group2.NumOfA();  // number of A: 7
    group2.NumOfB();  // number of B: 19
}

對于非常量靜態(tài)成員變量,C++11與C++98保持了一致景图,程序員需要到頭文件以外去定義它较雕,這會保證編譯時,類靜態(tài)成員的定義最后只會存在于一個目標文件中。

非靜態(tài)成員的 sizeof

  • C++98
    • 無法對非靜態(tài)成員變量使用sizeof
#include <iostream>
using namespace std;

struct People {
public:
    int hand;
    static People *all;
}

int main()
{
    Pople p;
    cout << sizeof(p.hand) << endl;       // C++98 ok, C++11 ok
    cout << sizeof(Pople::all) << endl;   // C++98 ok, C++11 ok
    cout << sizeof(People::hand) << endl; // C++98 err, C++11 ok
}

擴展的 friend 語法

friend 關(guān)鍵字用于聲明類的 友元亮蒋, 友元可以無視類中的成員屬性扣典。無論是public、protected或private慎玖,友元類或友元函數(shù)都可以訪問贮尖,這完全破壞了面向?qū)ο笾蟹庋b性的概念。通常趁怔,專家建議使用 Get/Set 方法訪問類成員湿硝,但是,friend會使程序員少些很多代碼痕钢。

class Poly;
typedef Poly P;

class LiLei {
    friend class Poly; // C++98 ok, C++11 ok
};
class Jim {
    friend Poly;       // C++98 error, C++11 ok
};
class HanMeiMei {
    friend P;          // C++98 error, C++11 ok
}

程序員可以為類模板聲明友元

class P;
template <typename T>
class People {
    friend T;
};

People<P> pp;   // 類型 P 在這里是 People 類型的友元
People<int> Pi; // 對于 int 類型模板參數(shù)图柏,友元聲明被忽略
// 為了方便測試,進行了危險的定義
#ifdef UNIT_TEST
#define private public
#endif
class Defender {
public:
    void Defender(int x, int y) {}
    void Trackle(int x, int y) {}

private:
    int pos_x = 15;
    int pos_y = 0;
    int speed = 2;
    int stamina = 120;
};

class Attacker {
public:
    void Move(int x, int y) {}
    void SpeedUp(float ration) {}

private:
    int pos_x = 0;
    int pos_y = -30;
    int speed = 3;
    int stamina = 100;
};

#ifdef UNIT_TEST
class Validator {
public:
    void Validate(int x, int y, Defender & d) { }
    void Validate(int x, int y, Attacker & a) { }
};

int main() {
    Defender d;
    Attacker a;
    a.Move(15, 30);
    d.Defence(15, 30);
    a.SpeedUp(1.5f);
    d.Defence(15, 30);
    Validator v;
    v.Validate(7, 0, d);
    v.Validate(1, -10, a);
    return 0;
}
#endif

將 private 關(guān)鍵字統(tǒng)一替換成了 public 關(guān)鍵字任连。
這是危險的蚤吹,在C++11中進行改良:

template <typename T>
class DefenderT {
public:
    friend T;
    void Defence(int x, int y) {}
    void Trackle(int x, int y) {}

private:
    int pos_x = 15;
    int pos_y = 0;
    int speed = 2;
    int stamina = 120;
};

template <typename T>
class AttackerT {
public:
    friend T;
    void Move(int x, int y) {}
    void SpeedUp(float ration) {}

private:
    int pos_x = 0;
    int pos_y = -30;
    int speed = 3;
    int stamina = 100;
};

using Defender = DefenderT<int>;  // 普通的類定義,使用 int 做參數(shù)
using Attacker = AttackerT<int>;

class Validator {
public:
    void Validate(int x, int y, Defender & d) { }
    void Validate(int x, int y, Attacker & a) { }
};

using DefenderTest = DefenderT<Validator>;  // 測試專用的定義随抠,Validator 類成為友元
using AttackerTest = AttackerT<Validator>;

int main() {
    Defender d;
    Attacker a;
    a.Move(15, 30);
    d.Defence(15, 30);
    a.SpeedUp(1.5f);
    d.Defence(15, 30);
    Validator v;
    v.Validate(7, 0, d);
    v.Validate(1, -10, a);
    return 0;
}

final/override 控制

  • 重載(overload):是指同一可訪問區(qū)內(nèi)被聲明的幾個具有不同參數(shù)列(參數(shù)的類型裁着,個數(shù),順序不同)的同名函數(shù)拱她,根據(jù)參數(shù)列表確定調(diào)用哪個函數(shù)二驰,重載不關(guān)心函數(shù)返回類型。
  • 重寫(override):派生類中存在重新定義的函數(shù)秉沼。其函數(shù)名桶雀,參數(shù)列表,返回值類型唬复,所有都必須同基類中被重寫的函數(shù)一致矗积。只有函數(shù)體不同(花括號內(nèi)),派生類調(diào)用時會調(diào)用派生類的重寫函數(shù)敞咧,不會調(diào)用被重寫函數(shù)棘捣。重寫的基類中被重寫的函數(shù)必須有virtual修飾。

virual 關(guān)鍵字

#include <iostream>

class A {
public:
    void print() {
        std::cout << "A" << std::endl;
    }
};

class B : public A {
public:
    void print() {
        std::cout << "B" << std::endl;
    }
};

int main()
{
    A *b = new B();

    b->print();  // A

    return 0;
}

將 print() 加上 virual 關(guān)鍵字修飾

#include <iostream>

class A {
public:
    virtual void print() {
        std::cout << "A" << std::endl;
    }
};

class B : public A {
public:
    void print() {
        std::cout << "B" << std::endl;
    }
};

int main()
{
    A *b = new B();

    b->print();  // B

    return 0;
}

說明休建,C++ 中多態(tài)函數(shù)必須使用 virtual 關(guān)鍵字進行修飾乍恐。

#include <iostream>
using namespace std;

class MathObject {
public:
    virtual double Arith() = 0;
    virtual void Prin() = 0;
};

class Printable : public MathObject {
public:
    double Arith() = 0;
    void Print() {  // C++98中我們無法阻止該接口被重寫
        cout << "Output is : " << Arith() << endl;
    }
};

class Add2 : public Printable {
public:
    Add2(double a, double b)
        : x(a),
          y(b) { }

    double Arith() {
        return x + y;
    }

private:
    double x, y;
};

class Mul3 : public Printable {
public:
    Mul3(double a, double b, double c)
        : x (a),
          y (b),
          z (c) {}

    double Arith() {
        return x * y * z;
    }
private:
    double x, y, z;
};

本來程序希望的結(jié)果是 Printable 重寫 Print() 方法,其他類不再重寫該方法测砂,而達到打印方式一致的目的茵烈。但是C++98中無法阻止 Print() 被重寫。

C++11 和 Java 類似砌些,通過 final 關(guān)鍵字阻止 函數(shù)繼續(xù)重寫瞧毙。

struct Object{
    virtual void fun() = 0;
};

struct Base : public Object {
    void fun() final;   // 聲明為final
};

struct Derived : public Base {
    void fun();     // 無法通過編譯
};

在 C++ 中,final 關(guān)鍵字同樣可以修飾虛函數(shù),但是虛函數(shù)的目的就是被重寫宙彪,如果用 final 修飾矩动,則失去了 虛函數(shù) 的意義。

基類聲明為 virtual 的函數(shù)释漆,之后的重寫不再需要聲明函數(shù)為 virtual悲没,即使派生類聲明為 virtual,該關(guān)鍵字也是編譯器可以忽略的。這導(dǎo)致了閱讀上的困難男图。程序員無法確定一個函數(shù)是虛函數(shù)還是非虛函數(shù)示姿。

C++11 為了幫助程序員寫繼承結(jié)構(gòu)復(fù)雜的類,引入了虛函數(shù)描述符 override逊笆,如果派生類在虛函數(shù)聲明時使用了 override 描述符栈戳,那么該函數(shù)必須重寫其基類的同名函數(shù),否則無法編譯通過难裆。

struct Base {
    virtual void Turing() = 0;
    virtual void Dijkstra() = 0;
    virtual void VNeumann(int g) = 0;

    virtual void DKnuth() const;
    void Print();
};

struct DerivedMid: public Base {
    // void VNeumann(double g);
    // 接口被隔離了子檀,曾想多一個版本的VNeumann函數(shù)
};

struct DerivedTop : public DerivedMid {
    void Turing() override;
    void Dikjstra() override;           // 無法通過編譯,拼寫錯誤乃戈,并非重寫
    void VNeumann(double g) override;   // 無法通過編譯褂痰,參數(shù)不一致,并非重寫
    void DKnuth() override;             // 無法通過編譯症虑,常量性不一致缩歪,并非重寫
    void Print() override;              // 無法通過編譯,非虛函數(shù)重寫
};

如果沒有 override 的修飾谍憔,程序員可能沒有意識到自己犯了這么多錯誤匪蝙。

模板函數(shù)的默認模板參數(shù)

#include <iostream>
using namespace std;

// 定義一個函數(shù)模板
template <typename T>
void TempFun(T a) {
    cout << a << endl;
}

int main() {
    TempFun(1);     // 1, (實例化為TempFun<const int>(1))
    TempFun("1");   // 1, (實例化為TempFun<const char *>("1"))
}

C++11 支持默認模板參數(shù)

void DefParm(int m = 3) {}  // c++98 - 編譯通過,c++11 - 編譯通過
template <typename T = int>
    class DefClass {};      // c++98 - 編譯通過习贫,c++11 - 編譯通過
template <typename T = int>
    void DefTempParm() {};  // c++98 - 編譯失敗逛球,c++11 - 編譯通過
template<typename T1, typename T2 = int> class DefClass1;
template<typename T1 = int, typename T2> class DefClass2;   // 無法通過編譯

template<typename T, int i = 0> class DefClass3;
template<int i = 0, typename T> class DefClass4;            // 無法通過編譯

/*
 * 類模板必須遵守 從右往左 的順序
*/

template<typename T1 = int, typename T2> void DefFunc1(T1 a, T2 b);
template<int i = 0, typename T> void DefFunc2(T a);
/*
 * 函數(shù)模板則沒有要求,隨意
*/

函數(shù)模板默認參數(shù)的推導(dǎo)由實參推導(dǎo)

template <class T, class U = double>
void f(T t = 0, U u = 0); // 同時使用了默認模板參數(shù)和默認模板函數(shù)參數(shù)

void g() {
    f(1, 'c');      // f<int,char>(1,'c')
    f(1);           // f<int,double>(1,0), 使用了默認模板參數(shù)double
    f();            // 錯誤: T無法被推導(dǎo)出來
    f<int>();       // f<int,double>(0,0), 使用了默認模板參數(shù)double
    f<int,char>();  // f<int,char>(0,0)
}

強調(diào)一點:模板函數(shù)的默認形參不是模板參數(shù)推導(dǎo)的依據(jù)沈条。

外部模板

為什么需要外部模板

C 中 extern 的目的:

extern int i;

一個文件定義 i, 多個文件聲明 i诅炉, 但是 i 只有一份數(shù)據(jù)蜡歹。

對函數(shù)模板來說,存在一模一樣的問題涕烧。不同的是月而,發(fā)生問題的不是變量,而是函數(shù)议纯。

// 在 test.h 聲明一個模板函數(shù)
template <typename T>
void fun(T) {}
//--------------------------------------------------------------

// test1.cpp
#include "test.h"
void test1() { fun(3); }
//--------------------------------------------------------------

// test2.cpp
#include "test.h"
void test2() { fun(4); }
//--------------------------------------------------------------
/*
 * 問題:
 * 由于兩個源代碼使用的模板函數(shù)的參數(shù)類型一致父款,所以再編譯 test1.cpp 時編譯器會實例化 fun<int>(int).
 * 在編譯 test2.cpp 時,編譯器會再一次實例化函數(shù) fun<int>(int)。
 * 那么結(jié)果就是 test1.o 和 test2.o 會有兩份一模一樣的函數(shù) fun<int>(int) 代碼.
*/

數(shù)據(jù)重復(fù)憨攒,編譯器往往無法分辨是否要共享數(shù)據(jù)世杀。

代碼重復(fù),為了節(jié)省空間肝集,保留其中之一就可以了瞻坝。事實上,大部分鏈接器也是這么做的杏瞻。鏈接器通過一些編譯器輔助的手段將重復(fù)的模板函數(shù)代碼 fun<int>(int) 刪除掉所刀,只保留了單個副本。

問題是:對于源碼中的每一處模板實例化捞挥,編譯器都需要去做實例化的工作;而在鏈接時砌函,鏈接器還需要刪除重復(fù)的實例化代碼。很明顯胸嘴,這太麻煩。

顯式的實例化與外部模板的聲明

顯示實例化(Explicit Instantiation)

template <typename T>
void fun(T) {
}

// 對 fun 模板顯式實例化
template <typename int>(int);
/*
 * 編譯器在本編譯單元中實例化出一個 fun<int>(int) 函數(shù)(強制實例化)劣像。
*/

// C++11 又加入了 外部模板(Extern Template)
extern template void fun<int>(int); // 外部模板聲明

我們重新修改下上述代碼:

// test1.cpp
template void fun<int>(int); // 顯式實例化
void test1() {
    fun(3);
}

//----------------------------------------------------------------

// test2.cpp
extern template void fun<int>(int);  // 外部模板聲明
void test() {
    fun(3);
}

注意問題:如果外部模板聲明出現(xiàn)在某個編譯單元中,那么與之對應(yīng)的顯式實例化必須出現(xiàn)于另一個編譯單元中或同一個編譯單元的后續(xù)代碼中绑青。

外部模板聲明不能用于一個靜態(tài)函數(shù)(文件域函數(shù)),但是可以用于類靜態(tài)成員函數(shù)(因為靜態(tài)函數(shù)沒有外部鏈接屬性闸婴,不能再本編譯單元外出現(xiàn))。

局部和匿名類型做模板實參

  • C++98:
    • 局部類型和匿名類型在C++98中不能做模板的實參
template <typename T>
class X {};

template <typename T>
void TempFun(T t){};

struct A{} a;
struct {int i;} b;           // b是匿名類型變量
typedef struct {int i;} B;   // B是匿名類型

void Fun()
{
    struct C {} c;          // C是局部類型

    X<A> x1;    // C++98 - 通過芍躏,C++11 - 通過
    X<B> x2;    // C++98 - 錯誤邪乍,C++11 - 通過
    X<C> x3;    // C++98 - 錯誤,C++11 - 通過
    TempFun(a); // C++98 - 通過对竣,C++11 - 通過
    TempFun(b); // C++98 - 錯誤庇楞,C++11 - 通過
    TempFun(c); // C++98 - 錯誤,C++11 - 通過
}

雖然匿名類型可以被模板參數(shù)接受了否纬,但以下寫法不被接受吕晌。

template <typename T>
struct MyTemplate { };

int main() { 
    // MyTemplate<struct { int a; }> t; // 無法編譯通過, 匿名類型的聲明不能在模板實參位置
    MyTemplate<decltype({struct { int a; }s;})> t; // 無法編譯通過, 匿名類型的聲明不能在模板實參位置
    return 0;
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市临燃,隨后出現(xiàn)的幾起案子睛驳,更是在濱河造成了極大的恐慌烙心,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件乏沸,死亡現(xiàn)場離奇詭異淫茵,居然都是意外死亡,警方通過查閱死者的電腦和手機屎蜓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進店門痘昌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人炬转,你說我怎么就攤上這事辆苔。” “怎么了扼劈?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵驻啤,是天一觀的道長。 經(jīng)常有香客問我荐吵,道長骑冗,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任先煎,我火速辦了婚禮贼涩,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘薯蝎。我一直安慰自己,他們只是感情好占锯,可當我...
    茶點故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布堡称。 她就那樣靜靜地躺著艺演,像睡著了一般胎撤。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上挺物,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天识藤,我揣著相機與錄音痴昧,去河邊找鬼赶撰。 笑死柱彻,一個胖子當著我的面吹牛哟楷,可吹牛的內(nèi)容都是我干的卖擅。 我是一名探鬼主播,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼断楷!你這毒婦竟也來了脐嫂?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎匀奏,沒想到半個月后娃善,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年蜒蕾,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片首启。...
    茶點故事閱讀 39,981評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡毅桃,死狀恐怖钥飞,靈堂內(nèi)的尸體忽然破棺而出衫嵌,到底是詐尸還是另有隱情渐扮,我是刑警寧澤,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站耻讽,受9級特大地震影響针肥,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜具则,卻給世界環(huán)境...
    茶點故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一博肋、第九天 我趴在偏房一處隱蔽的房頂上張望匪凡。 院中可真熱鬧病游,春花似錦衬衬、人聲如沸兼砖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽眼姐。三九已至佩番,卻和暖如春趟畏,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背利朵。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工绍弟, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留考抄,地道東北人川梅。 一個月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓贫途,卻偏偏與公主長得像丢早,于是被迫代替她去往敵國和親怨酝。 傳聞我的和親對象是個殘疾皇子那先,可洞房花燭夜當晚...
    茶點故事閱讀 44,933評論 2 355

推薦閱讀更多精彩內(nèi)容