【C++】 68_拾遺: 令人迷惑的寫法

TianSong發表於2019-05-10

令人迷惑的寫法 一

  • 下面的程式想要表達什麼意思呢?
template< class T >
class Test
{
public:
    Test(T t) {  } 
};

template < class T >
void func(T a[], int len)
{

}

程式設計實驗: class 模板初探

#include <iostream>
#include <string>

using namespace std;

template< class T >
class Test
{
public:
    Test(T t) 
    { 
        cout << "t = " << t << endl;
    } 
};

template < class T >
void func(T a[], int len)
{
    for(int i=0; i<len; i++)
    {
        cout << a[i] << endl;
    }
}

int main()
{
    Test<string> ts("D.T.Software");
    string ta[] = {"D", ".", "T", "."};
    
    func(ta, 4);
    
    Test<int> ti(100);
    int ai[] = {1, 2, 3, 4};
    
    func(ai, 4);
    
    return 0;
}
輸出:【編譯無錯誤,無警告】
t = D.T.Software
D
.
T
.
t = 100
1
2
3
4

分析:
class 定義的類别範本即可以適用於類型別,也可以適用於基礎型別(等同於 typename)
  • 歷史上的原因 。。。

    • 早期的 C++ 直接複用 class 關鍵字來定義模板
    • 但是泛型程式設計針對的不只是類型別
    • class 關鍵字的複用使得程式碼出現二義性

  • typename 誕生的直接原因

    • 自定義類型別內部的巢狀型別
    • 不同類中的同一個識別符號可能導致二義性
    • 編譯器無法辨識識別符號究竟是什麼

程式設計實驗: 模板中的二義性

#include <iostream>
#include <string>

using namespace std;

int a = 0;

class Test_1
{
public:
    static const int TS = 1;
};

class Test_2
{
public:
    struct TS
    {
        int value;
    };
};

template 
< class T >
void test_class()
{
    T::TS * a;    // 解讀方式 1 : 通過泛指型別 T 內部的資料型別 TS 定義指標變數 a (傾向的解讀方式)
                  // 解讀方式 2 : 通過泛指型別 T 內部的靜態成員變數 TS 與全域性變數 a 進行乘法操作
}                 

int main()
{
    test_class<Test_1>();    
    test_class<Test_2>();  // Error
    
    return 0;
}
輸出:
test.cpp: In function ‘void test_class() [with T = Test_2]’:
test.cpp:34:   instantiated from here
test.cpp:27: error: dependent-name ‘T::TS’ is parsed as a non-type, but instantiation yields a type
test.cpp:27: note: say ‘typename T::TS’ if a type is meant

結論:
T::TS * a; ==> 編譯器預設解析 TS 為成員變數
#include <iostream>
#include <string>

using namespace std;

int a = 0;

class Test_1
{
public:
    static const int TS = 1;
};

class Test_2
{
public:
    struct TS
    {
        int value;
    };
};

template 
< class T >
void test_class()
{
    typename T::TS * a;    // 解讀方式 1 : 通過泛指型別 T 內部的資料型別 TS 定義指標變數 a (傾向的解讀方式)
                           // 解讀方式 2 : 通過泛指型別 T 內部的靜態成員變數 TS 與全域性變數 a 進行乘法操作
}                 

int main()
{
    test_class<Test_1>();  // Error
    test_class<Test_2>();  
    
    return 0;
}
輸出:
test.cpp: In function ‘void test_class() [with T = Test_1]’:
test.cpp:33:   instantiated from here
test.cpp:27: error: no type named ‘TS’ in ‘class Test_1’

結論:
typename T::TS * a; ==> 明確告訴編譯器 typename 後為泛指型別
  • typename 的作用

    • 在模板定義中宣告泛指型別
    • 明確告訴編譯器其後的識別符號為型別

令人迷惑的寫法 二

下面的程式想要表達什麼意思呢?

int func(int i) try
{
    return i;
}
catch(...)
{
    cout << "Eception..." << endl;
}
int func(int i, int j) throw(int, char)
{
    return i + j;
}
  • try … catch 用於分隔正常功能程式碼與異常功能程式碼
  • try … catch 可以直接將函式分割為 2 部分
  • 函式宣告和定義時可以直接指定可能丟擲的異常型別
  • 異常宣告成為函式的一部分可以提高程式碼可讀性

  • 函式異常宣告的注意事項

    • 函式異常宣告是一種與編譯器之間的鍥約
    • 函式宣告異常後就只能丟擲宣告的異常

      • 丟擲其它型別異常將導致程式執行終止
      • 可以直接通過異常宣告定義無異常函式

程式設計實驗: 新的異常寫法

#include <iostream>
#include <string>

using namespace std;

int func(int i, int j) throw(int, char)
{
    if( (0 < j) && (j < 10) )
    {
        return (i + j);
    }
    else
    {
        throw `0`;
    }
}              

void test(int i) try
{
    cout << "func(i, i) = " << func(i, i) << endl;
}
catch(int i)
{
    cout << "Exception: " << i << endl;
}
catch(...)
{
    cout << "Exception..." << endl;
}

int main()
{
    test(5);
    test(10);

    return 0;
}
輸出:
func(i, i) = 10
Exception...

小結

  • class 可以用來在模板中定義泛指型別(不推薦)
  • typename 可以消除模板中的二義性
  • try… catch 可以將函式體分成 2 部分
  • 異常宣告能夠提高程式的可讀性

以上內容參考狄泰軟體學院系列課程,請大家保護原創!

相關文章