自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

告別冗長代碼!C++17 模板推導(dǎo)神器 CTAD,寫出令人驚艷的代碼

開發(fā)
C++17引入的類模板參數(shù)推導(dǎo)(Class Template Argument Deduction, CTAD)特性,可以讓編譯器自動推導(dǎo)模板參數(shù)類型,讓代碼更簡潔。

小王剛?cè)肼氁患一ヂ?lián)網(wǎng)公司,正在研究一段使用現(xiàn)代C++的代碼。他看著屏幕上的代碼一臉困惑。

"老張,你看這段代碼..." 小王指著顯示器說道。

"這是C++17引入的類模板參數(shù)推導(dǎo)(Class Template Argument Deduction, CTAD)特性," 老張解釋道,"它可以讓編譯器自動推導(dǎo)模板參數(shù)類型,讓代碼更簡潔。"

一、CTAD進(jìn)階示例

"老張,我還是不太明白CTAD具體能做什么..." 小王撓撓頭說

"來看個實際例子!" 老張笑著說

// 看這個常見場景
auto names = std::vector{"張三", "李四", "王五"}; // C++17 CTAD自動推導(dǎo)
auto scores = std::map{
    {"張三", 95},
    {"李四", 87},
    {"王五", 92}
}; // 自動推導(dǎo)為 map<const char*, int>

"哦!原來如此!" 小王眼睛一亮 "這比以前寫vector<string>和map<const char*, int>方便多了!"

"沒錯!" 老張點頭道 "CTAD最大的優(yōu)勢就是:

  • 代碼更短
  • 讓編譯器幫你推導(dǎo)類型
  • 減少人為錯誤"

"太棒了!" 小王興奮地說 "這就是所謂的編譯器幫我們干活!" 

"記?。褐灰獦?gòu)造函數(shù)參數(shù)類型明確,CTAD就能正確工作。" 老張總結(jié)道

二、與傳統(tǒng)寫法對比

"老張,這兩種寫法有什么區(qū)別???" 小王指著屏幕問道。

"來,我給你對比一下!" 老張笑著說

// 傳統(tǒng)寫法:又臭又長 ??
std::pair<int, double> p(2, 4.5);
std::tuple<int, int, double> t(4, 3, 2.5);

// CTAD寫法:簡潔優(yōu)雅 ?
std::pair p(2, 4.5);     
std::tuple t(4, 3, 2.5); 

"哇!這也太方便了吧!" 小王眼前一亮

"沒錯!" 老張點點頭,"現(xiàn)代C++就是要讓寫代碼變得更輕松"

"編譯器自動推導(dǎo)類型,既保證了類型安全,又讓代碼更清爽,一舉兩得!" 小王總結(jié)道 

三、自定義CTAD類實戰(zhàn)

"老張,自定義類也能用CTAD嗎?" 小王充滿好奇

"當(dāng)然可以!來看個實用例子!" 老張笑著說

// ??? 首先定義一個簡單的模板類
template<class T>
class MyBox {
    T data;
public:
    // ?? 構(gòu)造函數(shù)是CTAD工作的關(guān)鍵
    MyBox(T value) : data(value) {}
    
    // ?? 提供一個getter方法來訪問數(shù)據(jù)
    T get() const { return data; }
};

// 使用示例
MyBox box1(42);        // MyBox<int> 自動推導(dǎo) ?
MyBox box2("C++17");   // MyBox<const char*> 自動推導(dǎo) ?

"哦!原來這么簡單!" 小王眼睛一亮 "只要有構(gòu)造函數(shù),編譯器就能推導(dǎo)類型!"

"沒錯!" 老張點頭道 "記住三個要點:

  • 必須有構(gòu)造函數(shù) 
  • 參數(shù)類型要明確
  • 不能用{}初始化"

"學(xué)會了!" 小王開心地說 "這下寫模板類更輕松了!" 

四、CTAD的限制

"老張,我發(fā)現(xiàn)用花括號初始化時好像不太對勁..." 小王皺著眉頭說

"??!這是CTAD的一個重要限制,來看個例子!" 老張拿起鍵盤敲了起來

template<typename T, typename U>
struct Wrapper {
    T first;
    U second;
    Wrapper(T f, U s) : first(f), second(s) {}
};

// 這樣可以 ?
Wrapper w1(1, "hello");  // 自動推導(dǎo)為 Wrapper<int, const char*>

// 這樣不行 ?
Wrapper w2{1, "hello"};  // 編譯錯誤!

"為什么會這樣呢?" 小王追問道

"記住兩點就夠了:" 老張豎起兩根手指說

  • CTAD只支持圓括號初始化 ()
  • 花括號初始化 {} 必須手動指定類型

"原來如此!這下踩坑可以省了!" 小王松了一口氣

"沒錯,寫模板類時要牢記這個限制。" 老張笑著總結(jié)道

五、深入理解CTAD的花括號初始化限制

"為什么會這樣呢?" 小王追問道

"這是因為花括號初始化和圓括號初始化在C++中的行為有根本區(qū)別," 老張解釋道

"主要有兩個原因:

  • 花括號初始化會優(yōu)先考慮std::initializer_list 構(gòu)造函數(shù),這會影響類型推導(dǎo)的規(guī)則
  • 花括號初始化更嚴(yán)格,不允許窄化轉(zhuǎn)換(narrowing conversion),這增加了類型推導(dǎo)的復(fù)雜性

"讓我詳細(xì)解釋一下什么是窄化轉(zhuǎn)換(narrowing conversion)," 老張補(bǔ)充道

"窄化轉(zhuǎn)換是指可能導(dǎo)致數(shù)據(jù)精度丟失或數(shù)值范圍縮小的類型轉(zhuǎn)換。比如:

  • 浮點數(shù)轉(zhuǎn)整數(shù)
  • 大范圍整數(shù)轉(zhuǎn)小范圍整數(shù)
  • 有符號整數(shù)轉(zhuǎn)無符號整數(shù)

來看幾個例子:" 老張在鍵盤上敲了起來

template<typename T>
struct Value {
    T data;
    Value(T d) : data(d) {}
};

// 圓括號初始化:允許窄化轉(zhuǎn)換
Value v1(3.14);    // OK:double -> int,雖然小數(shù)部分丟失
Value v2(1000);    // OK:即使T是int8_t,也能編譯(可能溢出)

// 花括號初始化:禁止窄化轉(zhuǎn)換
Value v3{3.14};    // 錯誤:double不能窄化為int
Value v4{1000};    // 如果T是int8_t,編譯錯誤(超出范圍)

// 實際應(yīng)用場景
std::vector<int> vec1(3.14);    // OK:創(chuàng)建3個元素
std::vector<int> vec2{3.14};    // 錯誤:3.14不能窄化為int

"原來如此!" 小王說,"所以花括號初始化更安全,因為它能在編譯期就發(fā)現(xiàn)這些潛在的數(shù)據(jù)丟失問題!" 

"沒錯!" 老張贊許地點點頭,"這也是為什么在CTAD中處理花括號初始化會更復(fù)雜。編譯器需要:

  • 檢查是否存在窄化轉(zhuǎn)換
  • 確保類型推導(dǎo)不會導(dǎo)致數(shù)據(jù)丟失
  • 在有多個構(gòu)造函數(shù)時做出正確選擇"

"讓我先解釋一下std::initializer_list," 老張補(bǔ)充道

"std::initializer_list 是C++11引入的一個模板類,它允許我們使用花括號初始化器來初始化對象。它最常用于容器類的構(gòu)造和賦值。來看個例子:"

// std::initializer_list 的基本用法
std::vector<int> numbers{1, 2, 3, 4, 5};  // 使用初始化列表構(gòu)造

class MyContainer {
public:
    // 使用初始化列表構(gòu)造函數(shù)
    MyContainer(std::initializer_list<int> list) {
        for(int value : list) {
            // 處理每個元素
        }
    }
};

// 使用示例
MyContainer c{1, 2, 3, 4, 5};  // 使用花括號初始化

"當(dāng)一個類有接受std::initializer_list 的構(gòu)造函數(shù)時,花括號初始化會優(yōu)先選擇這個構(gòu)造函數(shù)。這就是為什么下面的代碼會有不同的行為:" 老張繼續(xù)解釋道

std::vector<int> v1(3, 5);    // 創(chuàng)建3個元素,每個值為5
std::vector<int> v2{3, 5};    // 創(chuàng)建2個元素:3和5

class Widget {
public:
    Widget(int i, int j) {}                           // #1
    Widget(std::initializer_list<int> il) {}          // #2
};

Widget w1(10, 20);    // 調(diào)用 #1
Widget w2{10, 20};    // 調(diào)用 #2,因為優(yōu)先選擇初始化列表構(gòu)造函數(shù)

"所以在CTAD中,當(dāng)使用花括號初始化時,編譯器需要:

  • 首先檢查是否存在initializer_list 構(gòu)造函數(shù)
  • 然后決定是否進(jìn)行窄化轉(zhuǎn)換檢查
  • 最后才能確定正確的模板參數(shù)類型

這就是為什么花括號初始化在CTAD中更復(fù)雜!" 老張總結(jié)道

六、CTAD的高級用法

"老張,我看到一些更復(fù)雜的CTAD用法,能給我解釋一下嗎?" 小王問道

"好的,讓我們一個一個來看這些高級場景!" 老張說

"首先是別名模板(alias template)的推導(dǎo):" 

// 1. 別名模板的推導(dǎo) 
template<class T>
using MyVector = std::vector<T>;  // 定義一個vector的別名 ??

MyVector v{1, 2, 3}; // 神奇吧! 自動推導(dǎo)為 MyVector<int> ?

"這太方便了!我們甚至可以給標(biāo)準(zhǔn)容器起個更簡短的名字!" 小王驚嘆道

"接著看一個更復(fù)雜的例子 - 非類型模板參數(shù)的推導(dǎo):" 老張繼續(xù)說

// 2. 非類型模板參數(shù)的推導(dǎo) 
template<class T>
struct Array {
    constexpr Array(T) {}  // 注意這個構(gòu)造函數(shù)是constexpr的 ??
};

template<Array x>  // 這里Array x是一個非類型模板參數(shù) ??
struct Container {}; 

Container<42> c;   // 編譯器會自動推導(dǎo)為 Container<Array<int>(42)> ??

"哇,這個有點難理解..." 小王撓撓頭

"沒關(guān)系,最后看個簡單的 - C++20引入的聚合類推導(dǎo):" 老張安慰道

// 3. 聚合類的推導(dǎo) (C++20新特性)
template<class T, class U>
struct Point {
    T x;  // 第一個成員 ??
    U y;  // 第二個成員 ??
};

Point p{1, 2.0};  // 看這里! 自動推導(dǎo)為 Point<int, double> ?
                  // x被推導(dǎo)為int, y被推導(dǎo)為double

"這個我理解了!" 小王眼睛一亮 "就像是自動識別每個成員的類型!" 

"沒錯!" 老張點頭笑道 "C++20讓CTAD變得更強(qiáng)大了!" 

七、CTAD的推導(dǎo)規(guī)則

"老張,編譯器是怎么知道該用什么類型呢?" 小王撓著頭問道

"這個問題問得好!" 老張笑著說 "CTAD的推導(dǎo)規(guī)則主要有三種,我們一個個來看:" 

1.隱式推導(dǎo)

template<class T>
struct Container {
    T value;
    Container(T v) : value(v) {} // ?? 從構(gòu)造函數(shù)推導(dǎo)
};

Container c1(42);     // ? 自動推導(dǎo)為 Container<int>
Container c2(c1);     // ?? 從復(fù)制構(gòu)造函數(shù)推導(dǎo)
Container c3(std::move(c1)); // ?? 從移動構(gòu)造函數(shù)推導(dǎo)

"看到了嗎?編譯器會自動從構(gòu)造函數(shù)的參數(shù)類型推導(dǎo)出模板參數(shù)!" 老張解釋道

2.用戶定義推導(dǎo)指引詳解

"有時候我們想要特殊的推導(dǎo)規(guī)則,就可以自己定義:" 老張繼續(xù)說

首先,定義一個基礎(chǔ)的智能指針模板類:

template<class T>
struct SmartPtr {
    T* ptr;
    SmartPtr(T* p) : ptr(p) {} // ?? 基礎(chǔ)構(gòu)造函數(shù)
};

這是最基本的模板類定義?,F(xiàn)在,我們想要對字符串指針做特殊處理:

// ?? 自定義推導(dǎo)規(guī)則
SmartPtr(char const*) -> SmartPtr<std::string>;  

// ?? 這個推導(dǎo)指引的含義是:
// - 當(dāng)看到 const char* 類型的參數(shù)時
// - 不要使用默認(rèn)的 SmartPtr<const char> 推導(dǎo)
// - 而是使用 SmartPtr<string> 作為目標(biāo)類型

使用示例:

SmartPtr ptr{"hello"};  // ? 推導(dǎo)為 SmartPtr<std::string>
                        // ?? 而不是 SmartPtr<const char>

讓我們再看一個更實用的例子 - 數(shù)組到 std::array 的自動轉(zhuǎn)換:

// ?? 基礎(chǔ)容器模板
template<typename T>
struct Container {
    T value;
};

// ?? 數(shù)組類型的特殊推導(dǎo)指引
template<typename T, std::size_t N>
Container(T (&)[N]) -> Container<std::array<T, N>>;

// ?? 這個推導(dǎo)指引會:
// - 捕獲原始數(shù)組的元素類型(T)和長度(N)
// - 將其轉(zhuǎn)換為等價的 std::array 類型

實際使用效果:

int arr[] = {1, 2, 3};
Container c(arr);  // ?? 自動推導(dǎo)為 Container<std::array<int, 3>>
                   // ? 而不是 Container<int[3]>

這樣的推導(dǎo)指引特別有用,因為:

  • 可以自定義特殊類型的轉(zhuǎn)換規(guī)則
  • 能夠優(yōu)化類型推導(dǎo)的結(jié)果
  • 提供更好的類型安全性和使用體驗

"原來如此!箭頭左邊是輸入?yún)?shù)的類型,右邊是我們期望推導(dǎo)出的具體類型!" 小王恍然大悟

"沒錯!" 老張點頭道,"推導(dǎo)指引讓我們能夠自定義CTAD的行為,特別適合處理那些需要特殊類型轉(zhuǎn)換的場景。" 

八、CTAD的最佳實踐

"老張,你能給我一些使用CTAD的實用建議嗎?" 小王問道

"當(dāng)然!讓我們一條一條來看," 老張笑著說 "首先是最基本的使用原則:" 

1. 優(yōu)先使用CTAD簡化代碼

// 傳統(tǒng)寫法:類型聲明太冗長了 ??
std::pair<int, double> p1(1, 2.5);
std::tuple<std::string, int, double> t1("hello", 1, 3.14);

// CTAD寫法:簡潔優(yōu)雅 ?
std::pair p2(1, 2.5);                    // 自動推導(dǎo)類型
std::tuple t2("hello", 1, 3.14);         // 讓編譯器來做這件事

"看到區(qū)別了嗎?CTAD能讓代碼更清晰易讀!" 老張指著屏幕說

2. 正確選擇初始化方式

"接下來要注意初始化語法的選擇," 老張繼續(xù)解釋道:

// 圓括號初始化 - CTAD的最佳搭檔 ?
std::vector v1(3, 42);        // 創(chuàng)建包含3個42的vector
std::pair p1(1, "hello");     // 完美推導(dǎo)類型

// 花括號初始化 - 需要小心使用 ??
std::vector v2{3, 42};        // 創(chuàng)建包含3和42兩個元素的vector
std::pair p2{1, "hello"};     // 某些情況可能推導(dǎo)失敗

"哇!原來初始化方式這么重要!" 小王恍然大悟

3. 自定義類型的推導(dǎo)指引

"對于自定義類型,我們還需要提供清晰的推導(dǎo)指引," 老張敲著鍵盤說:

// 首先定義一個模板類 ??
template<class T>
struct MyContainer {
    MyContainer(std::initializer_list<T> list) {}
};

// 添加推導(dǎo)指引 - 告訴編譯器如何推導(dǎo) ??
template<class T>
MyContainer(std::initializer_list<T>) -> MyContainer<T>;

// 現(xiàn)在可以愉快地使用了 ?
MyContainer c{1, 2, 3};  // 自動推導(dǎo)為 MyContainer<int>

"這樣編譯器就知道該怎么推導(dǎo)類型了!" 老張解釋道

"太棒了!" 小王興奮地說 "這些建議對我?guī)椭艽螅? 

"記住:CTAD是為了讓代碼更簡潔,但也要注意合理使用。" 老張總結(jié)道

總結(jié)

通過這次交流,小王對CTAD有了更直觀的認(rèn)識。這個特性確實讓代碼更簡潔易讀,是現(xiàn)代C++中非常實用的功能之一。主要優(yōu)點包括:

  • 減少冗余的類型聲明
  • 提高代碼可讀
  • 降低代碼出錯的可能性
  • 特別適合用于泛型編程

"明白了!" 小王恍然大悟,"這樣就不用每次都寫那么長的模板參數(shù)了。"

責(zé)任編輯:趙寧寧 來源: everystep
相關(guān)推薦

2024-02-21 20:43:02

Python列表推導(dǎo)式

2024-12-20 18:00:00

C++折疊表達(dá)式C++17

2024-12-18 06:00:00

C++17C++

2024-12-13 15:50:00

C++編程代碼

2024-12-20 07:30:00

C++17代碼

2024-12-27 12:00:00

C++17枚舉

2024-12-19 07:00:00

2024-12-30 08:10:00

C++17代碼文件

2024-12-27 09:12:12

C++17代碼元組

2021-01-04 07:57:07

C++工具代碼

2015-11-12 09:27:13

C++最新進(jìn)展

2020-08-04 07:47:59

代碼模板模式

2024-12-25 16:29:15

2014-11-26 10:23:09

2023-12-18 09:26:12

C++switchif

2023-12-18 10:11:36

C++17C++代碼

2020-07-15 08:17:16

代碼

2016-04-25 11:28:38

Ruby單行代碼

2022-03-10 12:39:48

NitruxLinuxLinux發(fā)行版

2024-02-20 08:46:54

點贊
收藏

51CTO技術(shù)棧公眾號