“強類型別名”(strong typedefs) 的動機和實現,配合一個簡單例子說明:
動機(Motivation)
- 用
using filename_t = string;
和using url_t = string;
來區分不同的字符串類型(比如文件名和網址),但本質上它們都是string
,類型不安全。 - 這導致函數重載會混淆,比如:
auto read(filename_t filename) { /* 從磁盤讀取 */ }
auto read(url_t url) { /* 從網絡讀取 */ }
auto filename = filename_t{"foobar.txt"};
auto url = url_t{"http://foobar.com/"};
cout << "From disk [" << filename << "]: " << read(filename) << endl;
cout << "From web [" << url << "]: " << read(url) << endl;
雖然看似區分了,但本質上 filename_t
和 url_t
都是 string
,可能被錯誤傳遞混用。
解決方案:強類型別名(Strong Typedef)
- 通過模板封裝,創建一個新的類型來包裹原類型,同時用標簽類區分不同的類型
template<typename T, typename Tag>
class strong_typedef {T value;
public:explicit strong_typedef(const T& v) : value(v) {}// 這里可以重載操作符,比如轉換、比較、輸出等const T& get() const { return value; }// 其他需要的操作
};
- 定義不同的類型別名:
using filename_t = strong_typedef<std::string, struct filename_t_tag>;
using url_t = strong_typedef<std::string, struct url_t_tag>;
- 函數參數也變成強類型:
auto read(filename_t filename) { /* 從磁盤讀取 */ }
auto read(url_t url) { /* 從網絡讀取 */ }
auto filename = filename_t{"foobar.txt"};
auto url = url_t{"http://foobar.com/"};
cout << "From disk [" << filename.get() << "]: " << read(filename) << endl;
cout << "From web [" << url.get() << "]: " << read(url) << endl;
好處
- 類型安全:編譯器區分
filename_t
和url_t
,防止混淆和誤用 - 可讀性強:代碼語義清晰
- 擴展性好:可以為不同的強類型定義不同的操作或行為
“代理(Proxy)”的動機和基本概念,結合示例講解:
Motivation - Proxies (代理的動機)
背景:
- 假設你有一百萬個對象,每個對象大小可能1 GB,或者有些很小。
- 直接存儲這么大的數據,內存消耗太大,管理也復雜。
例子
using LargeModel = map<string, Proxy<vector<double>>>; // 用代理包裹大對象
using SmallModel = map<string, array<double, 100>>; // 小對象用普通數組
平均計算函數,模板化
template<typename Model>
auto averageData(Model& model, std::string key) {auto& data = model[key]; // data 可能是 Proxy<vector<double>> 或 array<double, 100>double average = 0.0;for (auto element : data)average += element / data.size();return average;
}
這里 data
有兩種可能的類型:
- 代理類
Proxy<vector<double>>
- 或普通的
array<double, 100>
Proxy 類模板(簡化說明)
template<typename T>
class Proxy {// “魔法”部分,封裝 T 并代理它的行為// 可能實現:// - 延遲加載// - 訪問控制// - 緩存// - 遠程訪問// - 其他自定義邏輯
};
具體代理示例
Proxy<vector<double>> pvec;
Proxy<vector<int>> pvecInt;
Proxy<map<string, vector<float>>> pmap;
Proxy<SomeUserDefinedType> puser;
理解:
- 代理模式允許你把對某個“大對象”或復雜類型的訪問“包裹”起來,以控制訪問行為。
- 對調用者來說,好像直接訪問了數據結構,實際上背后可能有懶加載、緩存、遠程調用等操作。
- 這對節省內存、提高性能、封裝復雜邏輯都很有幫助。
#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <memory>
// Proxy 模板類:實現延遲加載(Lazy Loading)機制
template <typename T>
class Proxy {
private:std::unique_ptr<T> data_; // 智能指針管理真正的數據,初始為空// 延遲加載函數,首次訪問時調用,負責初始化數據void load() {if (!data_) { // 如果數據還沒加載std::cout << "Loading data...\n";data_ = std::make_unique<T>(); // 分配內存// 模擬加載數據:這里給 vector 填充數據for (int i = 0; i < 10; ++i) {data_->push_back(i * 1.1);}}}
public:Proxy() : data_(nullptr) {} // 構造函數,data_ 初始為空指針// 通過下標訪問元素,訪問前確保數據已經加載typename T::reference operator[](size_t index) {load();return (*data_)[index];}// 支持范圍 for 循環的 begin(),確保數據已加載typename T::iterator begin() {load();return data_->begin();}// 支持范圍 for 循環的 end(),確保數據已加載typename T::iterator end() {load();return data_->end();}// 返回容器大小,確保數據已加載size_t size() {load();return data_->size();}
};
// 定義兩種模型類型
// LargeModel 中的數據用 Proxy 包裹,支持延遲加載
using LargeModel = std::map<std::string, Proxy<std::vector<double>>>;
// SmallModel 中直接存儲 vector<double>
using SmallModel = std::map<std::string, std::vector<double>>;
// 模板函數:計算給定 key 對應數據的平均值
template <typename Model>
double averageData(Model& model, const std::string& key) {auto& data = model[key]; // 取出數據(可能是 Proxy 或 vector)double average = 0.0;for (auto element : data) // 遍歷數據,Proxy 支持迭代器訪問average += element / data.size();return average;
}
int main() {LargeModel largeModel;SmallModel smallModel;// smallModel 直接插入數據smallModel["small"] = {1, 2, 3, 4, 5};// largeModel 通過 Proxy 延遲加載,無需手動插入數據// 計算 smallModel 中 "small" 的平均值std::cout << "Average smallModel[small]: " << averageData(smallModel, "small") << "\n";// 計算 largeModel 中 "big" 的平均值,首次訪問時會觸發延遲加載std::cout << "Average largeModel[big]: " << averageData(largeModel, "big") << "\n";// 再訪問 largeModel 的具體元素,證明數據已加載,無需再次加載std::cout << "largeModel[big][3]: " << largeModel["big"][3] << "\n";return 0;
}
這段代碼實現了一個**延遲加載(Lazy Loading)**機制,通過模板類 Proxy<T>
包裝真實數據容器,實現“數據訪問時才加載”的效果。下面幫你詳細解釋:
1. Proxy
模板類
- 成員變量
std::unique_ptr<T> data_
:智能指針,管理真正的數據對象,初始化為空,表示數據還沒加載。 - 延遲加載函數
load()
- 每次訪問數據前調用
load()
,判斷data_
是否為空。 - 如果為空,說明數據還沒加載,輸出“Loading data…”,并創建一個新的數據容器對象(
std::vector<double>
)。 - 模擬數據加載,給容器填充數據(0, 1.1, 2.2, …, 9.9)。
- 每次訪問數據前調用
- 操作符重載與接口
operator[]
:通過下標訪問元素,訪問前先調用load()
確保數據已加載。begin()
和end()
:支持范圍for
循環,也會先調用load()
。size()
:獲取容器大小,同樣先確保數據已加載。
2. 模型定義
LargeModel
使用std::map<std::string, Proxy<std::vector<double>>>
,即用Proxy
包裹數據,實現延遲加載。SmallModel
使用std::map<std::string, std::vector<double>>
,直接存儲數據。
3. 模板函數 averageData
- 計算給定模型中指定
key
對應的數據的平均值。 - 這里的模型可以是
LargeModel
或SmallModel
,只要數據支持迭代訪問和size()
。 - 通過
auto& data = model[key];
取出數據(對于LargeModel
,這里會觸發Proxy
的operator[]
,進而調用load()
)。 - 使用范圍
for
循環計算平均值。
4. main
函數行為
smallModel
直接插入{1, 2, 3, 4, 5}
。- 調用
averageData(smallModel, "small")
,計算并打印平均值。 - 調用
averageData(largeModel, "big")
,首次訪問"big"
,觸發Proxy
的load()
,加載并填充數據,然后計算平均值。 - 再次訪問
largeModel["big"][3]
,此時數據已加載,不會再次打印“Loading data…”,直接返回對應元素。
代碼核心價值
- 延遲加載 避免了一開始就加載所有可能很大的數據,節省內存和時間。
- 透明代理 對外接口和普通容器一致,調用者無需關心數據是否已經加載。
- 模板泛型 同一函數支持多種數據模型,復用性好。
解釋一下“智能引用(smart reference)”和“智能指針(smart pointer)”的區別,以及為什么 C++ 中沒有“智能引用”的原因。
什么是智能指針?
智能指針,比如 std::shared_ptr<Foo>
,是對原始指針 Foo*
的封裝,自動管理對象的生命周期(比如自動釋放內存)。
- 你可以用
operator->()
訪問對象的成員,就像用指針一樣:std::shared_ptr<Foo> foo = ...; foo->bar(); // 使用箭頭操作符訪問成員
- 智能指針模仿指針的行為,支持指針的語義。
什么是智能引用?
- C++ 中的引用(
Foo&
)是對象的別名,本身不是對象,不能重新綁定到其他對象,也不能為 null。 - C++ 語言不支持重載“點操作符”(
operator.
),所以你不能實現像引用那樣直接用點號訪問成員的“智能引用”。 - 也就是說,沒有辦法寫出一個類,讓它像引用一樣直接用
foo.bar()
的語法來訪問成員。
通常,“智能引用”會提供: operator*()
來解引用,operator->()
來訪問成員指針,
例如:
smart_ref<Foo> foo = ...;
(*foo).bar(); // 用解引用操作符訪問
foo->bar(); // 用箭頭操作符訪問
但你不能寫成 foo.bar()
。
總結對比
特性 | 智能指針(smart pointer) | 智能引用(smart reference,假設存在) |
---|---|---|
模仿 | Foo* (指針) | Foo& (引用) |
訪問對象成員的操作符 | 支持 operator->() ,用箭頭訪問成員 | 支持 operator*() 和 operator->() ,但無 operator. |
能否重載點操作符 operator. | 不支持 | 不支持 |
是否能為 null | 可以 | 不可以 |
是否能重新綁定 | 可以 | 不可以 |
結論
- C++ 不支持重載“點操作符”,因此“智能引用”無法實現完全像普通引用一樣用
foo.bar()
訪問成員的語法。 - 智能引用只能通過
(*foo).bar()
或foo->bar()
訪問成員。 - 所以,如果你想封裝引用類型,并且希望像指針一樣操作,智能指針是可行的,智能引用只能有限度地模擬引用的行為。
**統一調用語法(Unified Call Syntax)**的問題,也就是函數式鏈式調用 vs. 嵌套函數調用。
兩種寫法對比:
auto s = sum(transform(filtered(unique(sorted(v)), is_even), squared));
- 這是典型的嵌套函數調用,從內到外依次執行。
- 缺點是讀者需要從最內層往外層一層層理解,閱讀難度較大。
- 優點是直接表達數據流轉過程,沒有中間變量。
auto s = v.sorted().unique().filtered(is_even).transform(squared).sum();
- 這是**鏈式調用(fluent interface)**風格,數據通過“點操作符”一環扣一環。
- 讀起來像一句話,順序執行,邏輯更直觀,更像自然語言。
- 代碼更清晰,更易于維護和擴展。
你更傾向哪個?
- 如果你喜歡函數式風格,且能很快“穿透”括號,第一種也挺自然。
- 如果你喜歡代碼簡潔、一步步串聯處理過程,第二種鏈式調用更易讀、理解。
額外補充
這種鏈式寫法一般需要背后有一個支持鏈式接口的容器或視圖類,比如 C++20 的 ranges 庫(std::ranges
)就支持類似的管道式操作,非常方便。
Proxy 模板類的設計和它如何和庫函數以及用戶自定義類型協同工作的示例。
核心點解析:
1. 隱式轉換(Implicit Conversion)
template<typename T>
class Proxy {T data;
public:operator T&() { /* lazy-load data... */ return data; }
};
- Proxy 持有實際數據
T data
。 - 實現了隱式轉換操作符
operator T&()
,當需要T&
類型時自動轉換,便于兼容普通函數接口。 - 例如:
auto foo(vector<double>& data) { /* ... */ }
auto proxy = Proxy<vector<double>>{/*...*/};
foo(proxy); // proxy 會隱式轉換為 vector<double>&
2. Proxy 繼承并暴露成員接口(Members)
template<typename T>
class Proxy : public using_<T> {T data;
public:operator T&() { /* lazy-load data... */ return data; }auto begin() { return data.begin(); }auto end() { return data.end(); }auto size() { return data.size(); }// 其他成員代理...
};
- 繼承了
using_<T>
(假設是個工具類,用來引入T
的類型定義,比如value_type
、iterator_type
等)。 - Proxy 直接提供了容器成員函數(
begin()
、end()
、size()
等)轉發到內部的data
。 - 這樣就可以像操作
vector
那樣操作 Proxy 了:
proxy.begin();
proxy[0] = 0;
back_inserter(proxy); // 支持標準庫算法
3. 用戶自定義類型支持(User-defined types)
假設有如下結構體:
struct Person {auto first_name() { /*...*/ }auto last_name() { /*...*/ }
};
REFLECTABLE(first_name);
REFLECTABLE(last_name);
- 這里
REFLECTABLE
可能是宏或機制用來生成或聲明反射接口(比如獲取成員函數列表)。 - Proxy 可以包裹
Person
:
auto proxy_person = Proxy<Person>{...};
cout << "First name: " << proxy_person.first_name() << endl;
cout << "Last name: " << proxy_person.last_name() << endl;
- 通過 Proxy 代理,用戶代碼不必關心是否在操作真實對象還是代理對象,調用語法完全一樣。
總結
- Proxy 類通過隱式轉換和成員函數轉發,做到對數據類型的透明包裝,實現懶加載、緩存、訪問控制等功能。
- 它既兼容庫函數(傳入需要真實容器引用的函數),也兼容成員函數調用(直接操作成員)。
- 適用于對數據訪問需要攔截或擴展功能的場景,同時對用戶來說接口無感知。
#include <iostream>
#include <vector>
#include <string>
#include <type_traits>
// 判斷類型T是否是容器,簡單判斷依據是T是否有value_type類型別名
template <typename, typename = void>
struct is_container : std::false_type {};
// 如果T中存在typename T::value_type,則判定為容器類型
template <typename T>
struct is_container<T, std::void_t<typename T::value_type>> : std::true_type {};
// 條件繼承:只有當T是容器時,才定義value_type、iterator、const_iterator等別名
template <typename T, bool = is_container<T>::value>
struct using_conditional {};
// 當T是容器時,繼承容器相關類型別名
template <typename T>
struct using_conditional<T, true> {using value_type = typename T::value_type;using iterator = typename T::iterator;using const_iterator = typename T::const_iterator;
};
// Proxy模板類,用于“代理”T類型對象,并延遲加載(lazy loading)
template <typename T>
class Proxy : public using_conditional<T> {mutable T data_; // mutable允許const成員函數也能修改data_,用于延遲加載// 延遲加載函數,針對特定類型做初始化操作void lazy_load() const {// 僅針對std::vector<double>做延遲加載示例if constexpr (std::is_same_v<T, std::vector<double>>) {if (data_.empty()) {std::cout << "Lazy loading vector<double> data...\n";data_ = {1.1, 2.2, 3.3, 4.4}; // 模擬從外部加載數據}}}
public:Proxy() = default;Proxy(const T& data) : data_(data) {}// 類型轉換運算符,使Proxy對象可以隱式轉換為底層數據引用operator T&() {lazy_load();return data_;}// 僅當T是容器時,提供begin()接口,支持范圍for循環auto begin() {if constexpr (is_container<T>::value) {lazy_load();return data_.begin();} else {static_assert(is_container<T>::value, "begin() only valid for containers");}}// 僅當T是容器時,提供end()接口,支持范圍for循環auto end() {if constexpr (is_container<T>::value) {lazy_load();return data_.end();} else {static_assert(is_container<T>::value, "end() only valid for containers");}}// 僅當T是容器時,提供size()接口auto size() {if constexpr (is_container<T>::value) {lazy_load();return data_.size();} else {static_assert(is_container<T>::value, "size() only valid for containers");}}// 調用底層非const成員函數的輔助函數模板template <typename Ret, typename... Args>Ret call(Ret (T::*func)(Args...), Args&&... args) {lazy_load();return (data_.*func)(std::forward<Args>(args)...);}// 調用底層const成員函數的輔助函數模板template <typename Ret, typename... Args>Ret call(Ret (T::*func)(Args...) const, Args&&... args) const {lazy_load();return (data_.*func)(std::forward<Args>(args)...);}
};
// 普通類型示例結構體Person,包含兩個const成員函數
struct Person {std::string first_name() const { return "John"; }std::string last_name() const { return "Doe"; }
};
int main() {// Proxy代理一個vector<double>,會觸發lazy_load初始化數據Proxy<std::vector<double>> proxy_vec;std::cout << "Size: " << proxy_vec.size() << "\n";// 范圍for循環,調用begin()和end()遍歷容器元素for (auto v : proxy_vec) std::cout << v << " ";std::cout << "\n";// Proxy代理一個普通類型Person實例Proxy<Person> proxy_person{Person{}};// 通過call調用Person的成員函數std::cout << "First name: " << proxy_person.call(&Person::first_name) << "\n";std::cout << "Last name: " << proxy_person.call(&Person::last_name) << "\n";return 0;
}
這段代碼實現了一個通用的 Proxy(代理)模板類,它可以代理任意類型 T
的對象,并實現延遲加載(Lazy Loading),尤其是針對容器類型(如 std::vector<double>
)進行特殊處理。
代碼作用總結:
- 判斷類型是否為容器
通過檢測類型T
是否定義了value_type
,用is_container
模板結構體判斷T
是否是容器類型(如std::vector
、std::list
等)。 - 條件繼承容器相關類型別名
只有當T
是容器時,Proxy<T>
才繼承定義value_type
、iterator
和const_iterator
等類型別名,使得Proxy
在接口上類似于容器。 - Proxy 類實現延遲加載功能
Proxy
持有一個T data_
成員,存儲實際數據。lazy_load()
函數負責“延遲加載”數據,在訪問數據時才初始化。例如,當T
是std::vector<double>
并且數據為空時,自動填充模擬數據{1.1, 2.2, 3.3, 4.4}
。- 使用
mutable
關鍵字允許在const
成員函數中修改data_
,支持延遲加載。
- 提供容器接口
- 如果
T
是容器,Proxy
提供begin()
,end()
,size()
等函數,并調用lazy_load()
保證數據已初始化。 - 這樣可以直接用范圍
for
循環遍歷Proxy
容器對象。
- 如果
- 提供成員函數調用接口
call
模板函數允許調用底層對象data_
的成員函數(包括const
和非const
),并自動延遲加載。- 通過傳入成員函數指針,可以訪問被代理對象的接口。
- 示例說明
- 用
Proxy<std::vector<double>>
演示代理容器,實現延遲加載和遍歷。 - 用
Proxy<Person>
演示代理普通類型,調用其成員函數。
- 用
具體效果:
- 當
proxy_vec
第一次調用size()
或開始遍歷時,lazy_load()
觸發,模擬加載數據。 - 通過
proxy_person.call()
可以調用被代理的Person
對象的成員函數,實現了對普通對象的代理訪問。
代理的優勢和應用:
- 節省資源:比如大數據結構,延遲加載避免不必要的開銷。
- 統一接口:無論是容器還是普通對象,使用相同的
Proxy
類管理。 - 靈活調用:支持成員函數調用,適合復雜業務場景。
你給的內容涉及一種設計模式,利用CRTP(Curiously Recurring Template Pattern,奇異遞歸模板模式)來實現一個零開銷的代理(Proxy)基類模板,方便給各種類型(比如 STL 容器或用戶自定義類型)提供統一的接口轉發,且盡量避免運行時開銷。
我幫你理清設計思路并逐段解釋:
1. 基礎代理基類示例(針對 vector<double>
)
class using_ {auto &delegate() {return static_cast<std::vector<double>&>(*this);}
public:virtual operator std::vector<double>&() = 0;auto begin() { return delegate().begin(); }auto end() { return delegate().end(); }
};
delegate()
用static_cast
將當前對象轉換成底層容器引用(這里固定為vector<double>
)。operator std::vector<double>&()
是純虛函數,要求派生類必須實現,返回實際的底層容器。begin()
和end()
通過調用delegate()
訪問底層容器的迭代器,實現迭代支持。
2. 派生具體代理類
class Proxy : public using_ {std::vector<double> data;
public:operator std::vector<double>&() override {// 延遲加載邏輯...return data;}
};
Proxy
繼承using_
并實現純虛轉換操作符,返回真實數據。- 實際數據存儲在
data
成員中。
3. 泛化基類模板,支持任意 STL 容器
template<typename Delegate>
class using_ {auto &delegate() { return static_cast<Delegate&>(*this); }
public:virtual operator Delegate&() = 0;auto begin() -> decltype(delegate().begin()) { return delegate().begin(); }auto end() -> decltype(delegate().end()) { return delegate().end(); }
};
using_
用模板參數Delegate
表示底層被代理的類型(例如vector<double>
、list<int>
等)。delegate()
返回Delegate&
,即當前類轉換為被代理類型的引用。- 迭代器接口也用
decltype
自動推導,保證泛化。
4. 使用 CRTP 實現零開銷代理基類
template<typename T>
class Proxy : public using_<T, Proxy<T>> {// Proxy具體實現
};
using_
變為兩個模板參數版,第二個參數是 CRTP 模式中的派生類自己。- 這樣可以利用靜態多態(編譯時多態)避免虛函數開銷。
template<typename Delegate, class CRTP>
class using_ {auto &delegate() {auto &derived = static_cast<CRTP&>(*this); // 向下轉換到派生類return static_cast<Delegate&>(derived); // 轉換為底層類型引用}
public:// 各種接口調用都通過delegate()轉發
};
- 這里的關鍵是
delegate()
通過兩次static_cast
完成派生類對象到底層委托類型的轉換,靜態解析實現高效轉發。
5. 支持用戶自定義類型的成員代理(多重繼承方式)
template<typename Delegate, typename CRTP, size_t index>
struct Member {};
template<typename Delegate, typename CRTP>
struct Member<Delegate, CRTP, 0> {auto &delegate() { ... }auto begin() -> decltype(delegate().begin()) { return delegate().begin(); }// 代理成員訪問...
};
- 利用
Member
模板結構體分別代理不同成員變量或成員接口。 - 通過多繼承,
using_
繼承多個Member<Delegate, CRTP, i>
,每個Member
對應一個成員或接口的代理,實現復雜類型的分解代理。
6. 結合宏實現反射式成員代理
#define REFLECTABLE(member) \
template<typename Delegate, typename CRTP> \
struct Member<Delegate, CRTP, __COUNTER__> { \auto &delegate() { ... } \auto member() -> decltype(delegate().member()) { return delegate().member(); } \
};
- 用
__COUNTER__
宏自動生成不同的Member
特化,實現成員的自動映射和代理。 - 使得用戶自定義類型的成員訪問變得自動化,類似簡單的“反射”功能。
總結
- 這套設計利用 CRTP 和模板元編程,實現了對任意類型的代理類基類設計,支持容器迭代、成員訪問等接口轉發。
- 利用靜態轉換和模板編譯時多態避免虛函數開銷,實現“零開銷代理”。
- 支持復雜用戶類型的成員代理,提供類似反射的自動成員映射。
- 適合設計高性能、靈活且通用的代理庫或視圖(view)層。
#include <iostream>
#include <vector>
#include <string>
// --- 基礎模板,用CRTP實現零開銷代理 ---
// Delegate:被代理的類型,CRTP:派生類類型(當前類)
// 通過CRTP實現靜態多態,避免虛函數開銷
template <typename Delegate, typename CRTP>
class using_ {
protected:// downcast到派生類,再轉成Delegate引用Delegate& delegate() {CRTP& derived = static_cast<CRTP&>(*this);return static_cast<Delegate&>(derived);}const Delegate& delegate() const {const CRTP& derived = static_cast<const CRTP&>(*this);return static_cast<const Delegate&>(derived);}
public:// 迭代器接口轉發,方便代理容器訪問auto begin() { return delegate().begin(); }auto end() { return delegate().end(); }auto begin() const { return delegate().begin(); }auto end() const { return delegate().end(); }// size() 轉發(假設被代理類型是容器)auto size() const { return delegate().size(); }
};
// --- 一個簡單的 Proxy 容器實現,繼承 using_ ---
// 該代理類持有T類型數據,并暴露T的接口
template <typename T>
class Proxy : public using_<T, Proxy<T>> {T data_; // 實際數據
public:Proxy() = default;Proxy(const T& data) : data_(data) {}// 轉換成底層類型引用,供using_調用operator T&() { return data_; }operator const T&() const { return data_; }
};
// --- 簡單的用戶自定義類型 ---
struct Person {std::string first_name;std::string last_name;int age;void print() const { std::cout << first_name << " " << last_name << ", age: " << age << "\n"; }
};
// --- 成員代理基類,用于訪問成員 ---
// 通過模板偏特化,為不同Index對應不同成員的訪問接口
template <typename Delegate, typename CRTP, size_t Index>
struct Member {}; // 默認空實現,防止未實現時報錯
// Member特化示例,代理first_name成員(Index=0)
template <typename Delegate, typename CRTP>
struct Member<Delegate, CRTP, 0> {// delegate()函數用于獲取被代理對象的引用auto& delegate() {CRTP& derived = static_cast<CRTP&>(*this);return static_cast<Delegate&>(derived);}// 提供first_name成員訪問接口auto& first_name() { return delegate().first_name; }const auto& first_name() const { return delegate().first_name; }
};
// Member特化示例,代理last_name成員(Index=1)
template <typename Delegate, typename CRTP>
struct Member<Delegate, CRTP, 1> {auto& delegate() {CRTP& derived = static_cast<CRTP&>(*this);return static_cast<Delegate&>(derived);}auto& last_name() { return delegate().last_name; }const auto& last_name() const { return delegate().last_name; }
};
// Member特化示例,代理age成員(Index=2)
template <typename Delegate, typename CRTP>
struct Member<Delegate, CRTP, 2> {auto& delegate() {CRTP& derived = static_cast<CRTP&>(*this);return static_cast<Delegate&>(derived);}auto& age() { return delegate().age; }const auto& age() const { return delegate().age; }
};
// --- 組合多個成員代理 ---
// 繼承多個Member特化,實現對多個成員的代理訪問
template <typename Delegate, typename CRTP>
class PersonProxy : public using_<Delegate, PersonProxy<Delegate, CRTP>>,public Member<Delegate, PersonProxy<Delegate, CRTP>, 0>,public Member<Delegate, PersonProxy<Delegate, CRTP>, 1>,public Member<Delegate, PersonProxy<Delegate, CRTP>, 2> {Delegate data_; // 持有被代理數據對象
public:PersonProxy() = default;PersonProxy(const Delegate& data) : data_(data) {}// 轉換操作符,允許隱式轉換成底層類型引用operator Delegate&() { return data_; }operator const Delegate&() const { return data_; }// 通過call接口調用成員函數(支持const成員函數)template <typename Ret, typename... Args>Ret call(Ret (Delegate::*func)(Args...) const, Args&&... args) const {return (data_.*func)(std::forward<Args>(args)...);}
};
// --- main演示 ---
int main() {// 容器代理示例Proxy<std::vector<int>> proxy_vec({10, 20, 30, 40});std::cout << "Vector size: " << proxy_vec.size() << "\n";for (auto v : proxy_vec) std::cout << v << " ";std::cout << "\n";// 用戶類型代理示例Person p{"Alice", "Smith", 30};// 構造代理對象,綁定Person實例PersonProxy<Person, PersonProxy<Person, void>> proxy_person(p);// 訪問成員變量代理接口std::cout << "Person first name: " << proxy_person.first_name() << "\n";std::cout << "Person last name: " << proxy_person.last_name() << "\n";std::cout << "Person age: " << proxy_person.age() << "\n";// 通過call調用成員函數print()proxy_person.call(&Person::print); // 可以通過代理對象調用成員函數
}
這段代碼實現了一個基于CRTP(Curiously Recurring Template Pattern)的零開銷代理設計模式,主要作用如下:
代碼作用總結
1. 實現一種通用代理機制,透明地訪問和操作底層對象
using_
類模板是代理基類,利用 CRTP 技巧實現接口轉發(如迭代器接口begin()
,end()
, 容器大小size()
等),實現零開銷的靜態多態。- 它讓派生類能夠無縫調用底層被代理對象的接口,且不需要運行時開銷(無虛函數)。
2. 實現對容器類型的代理包裝
Proxy<T>
模板類持有類型T
的實例(例如std::vector<int>
),并繼承using_
,將容器的接口代理出去。- 這樣,你可以通過
Proxy
對象像操作容器一樣操作底層數據,且中間無額外開銷。
3. 代理用戶自定義類型中的成員變量訪問
Member
模板通過偏特化,按Index
值映射到自定義類型(如Person
)的具體成員變量(first_name
,last_name
,age
)。- 每個特化
Member
類定義了對應成員變量的訪問函數(如first_name()
),實現成員訪問代理。
4. 通過繼承多個Member
特化,實現對多個成員的統一代理訪問
PersonProxy
類繼承了using_
和多個Member
,組合成對Person
所有重要成員的代理訪問接口。- 持有實際
Person
對象的數據,并通過call()
方法支持調用成員函數(比如print()
)。
5. 實例演示:
- 通過
Proxy<std::vector<int>>
訪問和操作std::vector<int>
,示范容器代理用法。 - 通過
PersonProxy
訪問Person
的成員變量并調用成員函數,示范用戶自定義類型的代理。 - 代碼示范了如何靈活、零開銷地實現成員訪問和方法調用代理。
總結
這段代碼展示了一個通用的靜態代理框架,能夠:
- 零開銷轉發容器接口(迭代器、大小等)
- 通過模板元編程,實現自定義類型成員變量的代理訪問
- 支持調用成員函數的代理調用接口
用法靈活且性能高效,適合需要代理訪問的復雜類型設計,且無需虛函數開銷。
提供的內容涉及用模板和宏來實現“反射”(reflection)機制的設計思路,尤其是解耦(uncoupling)反射邏輯與代理對象(delegate)的實現。下面幫你理清思路并總結關鍵點:
1. 宏定義 REFLECTABLE(member)
與 Member
特化
#define REFLECTABLE(member) ...
- 這是一個輔助宏,用來生成針對特定成員的
Member
模板特化。 __COUNTER__
宏在預處理時會自增,保證每次展開時的索引唯一,方便生成多個Member
特化。
template<typename Delegate, typename CRTP>
struct Member<Delegate, CRTP, __COUNTER__> {auto& delegate() { ... }auto member() -> decltype(delegate().member()) {return delegate().member();}
};
- 這是針對
member
成員的訪問代理,自動生成對應的訪問函數。 - 通過
delegate()
獲取實際被代理對象,再訪問它的member
成員。
2. 反射與代理對象解耦 — 設計思想
傳統方案
- 代理對象直接持有被代理對象(
delegate
),成員訪問直接映射到底層成員。 - 代理類中綁定了訪問邏輯和具體成員,耦合度較高。
解耦方案
- 將成員訪問操作抽象成“調用器(invoker)”或“操作器”。
- 代理類只負責調用
on_call(invoker)
接口,將操作交給傳入的調用器執行。 - 通過回調(
on_call
)機制,將訪問邏輯與數據分離。
3. 代碼示例解析
template<typename CRTP>
class Member<CRTP, 0> {auto& derived() { return static_cast<CRTP&>(*this); }
public:auto begin() {auto invoker = [](auto& obj) { return obj.begin(); };return derived().on_call(invoker);}
};
- 這是一個
Member
的特化,代理begin()
接口訪問。 - 它定義了一個
invoker
(lambda),表示對被代理對象執行begin()
操作。 - 通過
derived().on_call(invoker)
調用,將調用權交給持有具體對象的derived
對象。 derived()
即代理對象本身,負責調用on_call
,并傳入調用器invoker
。
template<typename Delegate, typename CRTP>
class using_ : public ... {template<typename Invoker>auto on_call(Invoker invoker) {return invoker(delegate(*this));}
};
using_
中實現了on_call
模板函數。on_call
接受一個調用器(lambda或函數對象),并傳入delegate(*this)
,即底層被代理對象。- 調用器執行實際操作并返回結果。
4. 總結理解
- 反射訪問操作與數據訪問分離,通過
on_call(invoker)
統一接口實現調用分發。 Member
模板只關心定義對某個成員或接口的訪問“調用器”。using_
模板類負責接收調用器并將其應用到底層數據對象。- 這種設計提高了靈活性和可擴展性,也方便加入各種訪問控制、延遲加載等邏輯。
REFLECTABLE
宏自動生成對應的Member
特化,減少模板編寫重復。
這部分內容展示了 “統一調用語法(Unified Call Syntax)” 的設計思路,它通過將成員調用和非成員函數調用封裝成統一的調用接口,實現鏈式調用和靈活操作的風格。我們一步步來理解:
1. Member 類模板里統一調用器(ComposedInvoker)
template<typename CRTP>
class Member<CRTP, 0> {
public:auto begin() {// 成員調用的調用器auto invoker_member = [](auto &obj) { return obj.begin(); };// 非成員調用的調用器auto invoker_nonmember = [](auto &obj) { return begin(obj); };// 組合調用器,繼承成員調用的lambdastruct ComposedInvoker : decltype(invoker_member) {using decltype(invoker_member)::operator();// 提供轉換為非成員調用器的方法auto as_nonmember() { return invoker_nonmember; }};// 調用代理對象的on_call,將調用器傳入return derived().on_call(ComposedInvoker{});}
};
invoker_member
是針對成員函數調用的lambda,比如obj.begin()
。invoker_nonmember
是對應的非成員函數調用,比如begin(obj)
。ComposedInvoker
繼承成員調用器,額外實現as_nonmember()
,可以切換成非成員調用版本。- 最終調用
derived().on_call()
,將調用器傳給代理對象處理。
2. UnifiedCallSyntax 類中調用器處理
template<typename T>
class UnifiedCallSyntax : public using_<T, UnifiedCallSyntax<T>> {
public:template<typename Invoker>auto on_call(Invoker invoker) {// 使用調用器的非成員版本,傳入底層對象auto invoker_nonmember = invoker.as_nonmember();auto result = invoker_nonmember(this->delegate());// 返回一個新的 UnifiedCallSyntax 包裝結果,實現鏈式調用return UnifiedCallSyntax{result};}
};
- 這里的
on_call
接收調用器,調用它的as_nonmember()
方法,得到對應的非成員調用版本。 - 用非成員調用版本操作底層對象
delegate()
。 - 返回一個新的
UnifiedCallSyntax
實例,封裝操作結果,支持鏈式調用。
3. 非成員算法鏈式調用示例
auto sort(const auto &container) { ... }
auto unique(const auto &container) { ... }
// 其他類似的算法...
template<typename T>
using $ = UnifiedCallSyntax<T>;
auto v = vector{1,2,2,1,3,2,4,5};
auto s = $(v).sort().unique().filter(is_even).transform(squared).sum();
- 利用
UnifiedCallSyntax
包裝容器v
。 - 依次調用封裝的算法(
sort
、unique
、filter
等),形成鏈式調用。 - 每個調用都返回新的
UnifiedCallSyntax
對象,支持連續調用。 - 統一成員調用和非成員函數調用的接口,寫法簡潔流暢。
總結
- 統一調用語法通過
ComposedInvoker
封裝成員調用和非成員調用,實現靈活切換。 on_call
使用非成員調用版本執行操作,并返回包裝結果,保證鏈式調用。- 用戶可以像調用成員函數一樣調用算法,同時實際執行的是非成員算法,增強代碼的可讀性和表達力。
- 這是函數式風格和面向對象風格結合的優秀設計,特別適合容器算法的靈活組合。
#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
// 所有算法定義在命名空間alg里,避免和類成員函數名沖突
namespace alg {
// 非成員算法:排序,返回排序后的新容器副本
template <typename Container>
auto sort(const Container& c) {auto v = c; // 拷貝輸入容器,保持不變std::sort(v.begin(), v.end());return v; // 返回排序結果
}
// 非成員算法:去重,返回新容器副本
template <typename Container>
auto unique(const Container& c) {auto v = c; // 拷貝輸入容器v.erase(std::unique(v.begin(), v.end()), v.end());return v; // 返回去重結果
}
// 非成員算法:過濾,接受謂詞pred,返回滿足條件的新容器副本
template <typename Container, typename Pred>
auto filter(const Container& c, Pred pred) {// 結果容器元素類型推斷,自動匹配輸入容器元素類型std::vector<std::decay_t<decltype(*c.begin())>> v;for (auto& e : c)if (pred(e)) v.push_back(e);return v; // 返回過濾后的容器
}
// 非成員算法:變換,對容器元素應用op,返回變換結果容器
template <typename Container, typename Op>
auto transform(const Container& c, Op op) {// 推斷變換后元素類型std::vector<std::decay_t<decltype(op(*c.begin()))>> v;v.reserve(c.size()); // 預留空間提高效率for (auto& e : c) v.push_back(op(e));return v; // 返回變換結果
}
// 非成員算法:求和,返回累加值
template <typename Container>
auto sum(const Container& c) {using T = std::decay_t<decltype(*c.begin())>;return std::accumulate(c.begin(), c.end(), T{0});
}
} // namespace alg
// 統一調用語法包裝類,封裝任意類型T的數據
template <typename T>
class UnifiedCallSyntax {T data_; // 底層數據
public:UnifiedCallSyntax(T data) : data_(std::move(data)) {}// 訪問底層數據T& delegate() { return data_; }const T& delegate() const { return data_; }// 統一調用接口,接收調用器,執行調用,返回新的包裝對象支持鏈式template <typename Invoker>auto on_call(Invoker invoker) {auto result = invoker(data_); // 調用傳入調用器return UnifiedCallSyntax{std::move(result)}; // 用調用結果構造新的包裝}// 排序操作,調用alg::sort,支持鏈式調用auto sort() {// lambda顯式捕獲this,調用alg::sortauto invoker = [this](auto& obj) { return alg::sort(obj); };return on_call(invoker);}// 去重操作auto unique() {auto invoker = [this](auto& obj) { return alg::unique(obj); };return on_call(invoker);}// 過濾操作,傳入謂詞template <typename Pred>auto filter(Pred pred) {auto invoker = [this, pred](auto& obj) { return alg::filter(obj, pred); };return on_call(invoker);}// 變換操作,傳入變換函數template <typename Op>auto transform(Op op) {auto invoker = [this, op](auto& obj) { return alg::transform(obj, op); };return on_call(invoker);}// 求和操作,終結操作,返回值而非新的包裝對象auto sum() const { return alg::sum(data_); }// 獲取底層數據常量引用const T& get() const { return data_; }
};
int main() {std::vector<int> v = {1, 2, 2, 1, 3, 2, 4, 5};// 使用統一調用語法鏈式調用各算法auto result = UnifiedCallSyntax{v}.sort().unique().filter([](int x) { return x % 2 == 0; }) // 過濾偶數.transform([](int x) { return x * x; }) // 平方.sum(); // 求和std::cout << "Result: " << result << "\n"; // 輸出結果return 0;
}
這段代碼實現了一個“統一調用語法”的容器適配器,支持鏈式調用一系列算法(排序、去重、過濾、變換、求和),讓對容器的操作寫得簡潔流暢。
主要組成部分和邏輯:
1. 命名空間 alg
放置了一系列非成員算法函數,針對任意容器:
sort
:復制容器并排序,返回新容器unique
:復制容器并去重,返回新容器filter
:根據傳入謂詞過濾元素,返回新容器transform
:對容器元素應用變換操作,返回新容器sum
:對容器元素求和,返回累加值
這些函數都不修改傳入容器,而是返回一個新的處理后的結果。
2. 類模板 UnifiedCallSyntax<T>
封裝一個類型為 T
的容器對象,并通過成員函數提供一套鏈式調用的接口。
- 構造和成員變量
- 構造時接受一個容器副本
data_
(通過移動避免額外拷貝)
- 構造時接受一個容器副本
delegate()
- 返回對底層數據的引用,方便內部調用
on_call(Invoker invoker)
- 這是一個“統一調用接口”,接受一個函數對象
invoker
,調用它并傳入當前容器data_
- 得到結果后,返回一個包裹結果的新的
UnifiedCallSyntax
對象,從而實現鏈式調用
- 這是一個“統一調用接口”,接受一個函數對象
- 算法成員函數:
sort()
,unique()
,filter()
,transform()
- 這些函數構造對應的調用器(lambda),通過
on_call
調用非成員算法,返回新包裝對象 - 過濾和變換接受函數對象參數(謂詞或變換操作),通過捕獲傳入
- 這些函數構造對應的調用器(lambda),通過
- 終結操作:
sum()
- 直接調用非成員算法返回結果(非包裝對象),不支持鏈式調用后續操作
- 訪問底層數據
get()
返回底層容器的常量引用
3. main()
測試用例
- 初始化一個整型向量
v
- 使用
UnifiedCallSyntax{v}
包裝容器,鏈式調用:.sort()
先排序.unique()
去重.filter([](int x){ return x % 2 == 0; })
過濾偶數.transform([](int x){ return x * x; })
平方.sum()
求和,得到最終結果
- 輸出最終結果
總結:
- 通過非成員函數+統一包裝分離算法和容器數據
- 通過模板和
on_call
實現鏈式調用,返回新包裝對象 - 算法返回值被連續傳遞,保持鏈式操作的流暢和類型安全
- 代碼簡潔,易擴展新的算法或操作