当前位置: 首页 > news >正文

CppCon 2018 学习:Smart References

“强类型别名”(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_turl_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_turl_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 对应的数据的平均值。
  • 这里的模型可以是 LargeModelSmallModel,只要数据支持迭代访问和 size()
  • 通过 auto& data = model[key]; 取出数据(对于 LargeModel,这里会触发 Proxyoperator[],进而调用 load())。
  • 使用范围 for 循环计算平均值。

4. main 函数行为

  • smallModel 直接插入 {1, 2, 3, 4, 5}
  • 调用 averageData(smallModel, "small"),计算并打印平均值。
  • 调用 averageData(largeModel, "big"),首次访问 "big",触发 Proxyload(),加载并填充数据,然后计算平均值。
  • 再次访问 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_typeiterator_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>)进行特殊处理。

代码作用总结:

  1. 判断类型是否为容器
    通过检测类型 T 是否定义了 value_type,用 is_container 模板结构体判断 T 是否是容器类型(如std::vectorstd::list等)。
  2. 条件继承容器相关类型别名
    只有当 T 是容器时,Proxy<T> 才继承定义 value_typeiteratorconst_iterator 等类型别名,使得 Proxy 在接口上类似于容器。
  3. Proxy 类实现延迟加载功能
    • Proxy 持有一个 T data_ 成员,存储实际数据。
    • lazy_load() 函数负责“延迟加载”数据,在访问数据时才初始化。例如,当 Tstd::vector<double> 并且数据为空时,自动填充模拟数据 {1.1, 2.2, 3.3, 4.4}
    • 使用 mutable 关键字允许在 const 成员函数中修改 data_,支持延迟加载。
  4. 提供容器接口
    • 如果 T 是容器,Proxy 提供 begin(), end(), size() 等函数,并调用 lazy_load() 保证数据已初始化。
    • 这样可以直接用范围 for 循环遍历 Proxy 容器对象。
  5. 提供成员函数调用接口
    • call 模板函数允许调用底层对象 data_ 的成员函数(包括 const 和非 const),并自动延迟加载。
    • 通过传入成员函数指针,可以访问被代理对象的接口。
  6. 示例说明
    • 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
  • 依次调用封装的算法(sortuniquefilter等),形成链式调用。
  • 每个调用都返回新的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 调用非成员算法,返回新包装对象
    • 过滤和变换接受函数对象参数(谓词或变换操作),通过捕获传入
  • 终结操作: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实现链式调用,返回新包装对象
  • 算法返回值被连续传递,保持链式操作的流畅和类型安全
  • 代码简洁,易扩展新的算法或操作
http://www.lryc.cn/news/581269.html

相关文章:

  • 有限状态机(Finite State Machine)
  • 相机位姿估计
  • 2 大模型高效参数微调;prompt tunning
  • 【Linux】自旋锁和读写锁
  • 全素山药开发指南:从防痒处理到高可用食谱架构
  • DeepSeek扫雷游戏网页版HTML5(附源码)
  • C#指针:解锁内存操作的底层密码
  • 机械时代的计算
  • 【Linux】常用基本指令
  • 爬虫工程师Chrome开发者工具简单介绍
  • 推荐算法系统系列五>推荐算法CF协同过滤用户行为挖掘(itembase+userbase)
  • Python实例题:基于 Python 的简单电子词典
  • 洛谷刷题9
  • Django中关于templates目录和static目录存放位置的总结
  • Django跨域
  • python使用fastmcp包编写mcp服务端(mcp_server)和mcp客户端(mcp_client)
  • jxWebUI--用数据表输入输出数据
  • 前端进阶之路-从传统前端到VUE-JS(第三期-VUE-JS配套UI组件的选择)(Element Plus的构建)
  • SQL 表结构转 Go、Java、TS 自定义实体类,支持自编模板
  • 学习日志04 python
  • 解决kali Linux在VMware中的全局缩放问题
  • Linux:多线程---深入互斥浅谈同步
  • jvm架构原理剖析篇
  • Python之--基本知识
  • App爬虫实战篇-以华为真机手机爬取集换社的app为例
  • 11_架构演进:从单体到云原生的蜕变
  • 【Docker基础】Docker数据卷管理:docker volume prune及其参数详解
  • Apache 配置文件提权的实战思考
  • Feign调用报“请求方法POST不支持“错误
  • 在sf=0.1时测试fireducks、duckdb、polars的tpch