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;
}