CppCon 2014 学习:Practical Type Erasure A boost::any Based Configuration Framework

Practical Type Erasure” 中提到的 基于 boost::any 的配置框架,是一个典型的 类型擦除(Type Erasure) 应用案例。下面是详细解释,帮助你理解其作用和原理。

什么是 Type Erasure?

Type Erasure(类型擦除) 是一种技术,它允许你在不知道对象具体类型的情况下,依然能安全地存储、传递和操作这个对象。

使用场景:配置框架(Configuration Framework)

你想写一个通用配置系统,允许用户存储不同类型的配置项,比如:

config["port"] = 8080;               // int
config["host"] = "localhost";       // std::string
config["debug"] = true;             // bool
config["timeout"] = 3.5;            // double

但这些值的类型都是不一样的,怎么办?

Boost.Any 的作用

boost::any 是 C++98/03 时期 Boost 库提供的类型擦除容器,可以在运行时安全地存储任何类型的值

#include <boost/any.hpp>
#include <map>
#include <string>
std::map<std::string, boost::any> config;
config["port"] = 8080;
config["host"] = std::string("localhost");
config["debug"] = true;

存储任意类型的值 —— 类型擦除!

但你还可以这样取值:

int port = boost::any_cast<int>(config["port"]);
std::string host = boost::any_cast<std::string>(config["host"]);

如果类型不匹配,any_cast 会抛出 boost::bad_any_cast 异常。

典型实现:基于 boost::any 的配置系统

class Config {
public:
    template<typename T>
    void set(const std::string& key, const T& value) {
        data_[key] = value;  // 隐式转为 boost::any
    }

    template<typename T>
    T get(const std::string& key) const {
        return boost::any_cast<T>(data_.at(key));
    }

private:
    std::map<std::string, boost::any> data_;
};

使用:

Config config;
config.set("port", 8080);
config.set("title", std::string("My App"));
int port = config.get<int>("port");
std::string title = config.get<std::string>("title");

小结

项目描述
目标存储任意类型的配置项
技术类型擦除(Type Erasure)
工具boost::any(或现代 C++ 的 std::any
优点灵活、不需要模板或继承
缺点运行时开销大,类型错误在运行时才会发现

提示

  • 在 C++17 中,建议使用 std::any 替代 boost::any,语义相同。
  • 如果你想做类型安全或支持更多抽象操作,可以用 std::functionstd::variant 或自定义接口实现更复杂的 Type Erasure 框架。

你提到的这段代码展示了 Type Erasure(类型擦除) 的基本结构。下面是对这段代码的详细讲解和背后的思想分析:

你给出的代码:

class C {
public:
    template<typename TInject>
    C(TInject injectedInstance);
    void invoke();
};

这段代码的核心概念:类型擦除

目标:类 C 能够接受任意类型的 injectedInstance,但你又希望在 invoke() 中以统一的方式调用它的某些功能,不关心它具体是什么类型。
这就是典型的 “Type Erasure”:把不同类型的对象“隐藏”在一个统一接口背后。

如何实现?

这段代码只是框架。要真正实现类型擦除,通常会有以下结构:

完整实现步骤(简化版)

#include <memory>
#include <iostream>

// 1. 定义统一的接口
class ICallable {
public:
    virtual void invoke() = 0;
    virtual ~ICallable() = default;
};

// 2. 包装任意类型的对象
template<typename T>
class CallableImpl : public ICallable {
public:
    CallableImpl(T obj) : obj_(obj) {}

    void invoke() override {
        obj_(); // 假设传进来的 T 是可调用的(函数/函数对象/lambda)
    }

private:
    T obj_;
};

// 3. 类型擦除类
class C {
public:
    template<typename TInject>
    C(TInject injectedInstance)
        : impl_(std::make_shared<CallableImpl<TInject>>(injectedInstance)) {}

    void invoke() {
        impl_->invoke();
    }

private:
    std::shared_ptr<ICallable> impl_;
};

使用方式:

void hello() {
    std::cout << "Hello, world!" << std::endl;
}

int main() {
    C c1(hello); // 传入函数指针
    C c2([]{ std::cout << "Lambda called!" << std::endl; }); // 传入 lambda
    c1.invoke();  // 输出 Hello, world!
    c2.invoke();  // 输出 Lambda called!
}
概念说明
Type Erasure编译期隐藏具体类型,仅暴露统一接口
目的让类 C 接收任意类型,并通过统一方式操作它们
关键技术接口类 + 模板包装器 + 智能指针
实际用途类似于 std::functionstd::any、策略模式

这段代码是一个 Type Erasure(类型擦除) 的实际实现,它让类 C 可以接受任意类型的对象,只要这些对象有 Deploy() 方法。下面是详细讲解:

你给出的结构精简整理后如下:

class C {
public:
    template<typename TInject>
    C(TInject injectedInstance)
        : m_internalBasePtr(std::make_shared<CInjected<TInject>>(injectedInstance)) {}
    void invoke() {
        m_internalBasePtr->DoIt();
    }
private:
    struct CInternalBase {
        virtual void DoIt() = 0;
        virtual ~CInternalBase() {}
    };
    template<typename TInjected>
    struct CInjected : public CInternalBase {
        CInjected(TInjected i) : m_injected(i) {}
        virtual void DoIt() override {
            m_injected.Deploy();  // 要求 TInjected 有 Deploy() 方法
        }

        TInjected m_injected;
    };
    std::shared_ptr<CInternalBase> m_internalBasePtr;
};

这个实现做了什么?

1. 目的:

通过模板 + 虚函数,让类 C 能持有任意类型的对象,只要该对象提供 Deploy() 方法。

2. 关键组件解释

成员作用
CInternalBase抽象基类,定义了统一的接口 DoIt()
CInjected<T>模板派生类,用于包装任意类型 T,并实现 DoIt()
m_internalBasePtr用于保存实际类型的对象,类型被擦除了。
C::invoke()调用 DoIt(),触发实际对象的 Deploy() 方法。

示例用法

你可以这么用这个类:

struct MyType {
    void Deploy() {
        std::cout << "MyType deployed!" << std::endl;
    }
};

int main() {
    MyType obj;
    C c(obj);     // 类型被擦除
    c.invoke();   // 调用的是 obj.Deploy()
}

输出:

MyType deployed!

总结:

知识点内容
Type Erasure使用模板+继承+虚函数,把具体类型“隐藏”在统一接口后面
优点C 现在可以存储任意类型,只要它实现 Deploy()
实现技巧使用模板类继承接口类 + shared_ptr 管理生命周期
类似案例std::function, std::any, std::shared_ptr 的内部实现机制

下面是std::any 改写后的完整代码

#include <vector>
#include <string>
#include <iostream>
#include <any>  // 使用 C++17 的 std::any
int main()
{
    std::any a = std::string("Anything?");
    std::vector<std::string> v = {"Anything!"};
    a = v;
    a = 5;  // 最终 a 中存储的是 int
    std::cout << std::any_cast<int>(a) << std::endl;
    return 0;
}

注意事项:

  1. 头文件改为 <any>
  2. 类型从 boost::any 改为 std::any
  3. std::any_cast<T>(a) 的用法和 boost::any_cast 基本一致。
  4. 要启用 C++17 标准 编译(否则编译器会报错):
编译方式示例(GCC 或 Clang):
g++ -std=c++17 your_file.cpp -o your_program

总结

  • std::any 是 C++17 引入的用于类型擦除的类型容器。
  • 能够存储任意类型的对象(复制构造可用的),并且通过 std::any_cast 进行类型安全的访问。
  • 替代 boost::any,可以减少对 Boost 的依赖。

Type Erasure(类型擦除) 在 C++ 中的实际应用场景。我们逐项来解释这些内容,帮助你理解这个概念的实际意义:

什么是 Type Erasure?

Type Erasure(类型擦除) 是一种设计技术,它允许你隐藏类型信息,以便不同类型的对象看起来像一个类型。你可以把它理解为一种让 C++ 支持运行时多态而不使用继承和虚函数表的方式。

实际应用场景(Practical Use Cases)

这些都是 Type Erasure 的经典应用:

1. Smart Pointer Deletion(智能指针删除器)
std::unique_ptr<Base, Deleter> ptr;
  • Deleter 是一个可以自定义的 Callable。
  • std::unique_ptr 使用 类型擦除来存储 Deleter,它不在乎你用的是 lambda、函数指针,还是其他对象。
  • Type-erased deleter 允许 unique_ptr 支持不同的删除策略。
2. Heterogeneous Containers(异构容器)
std::vector<std::any> values;
  • 容器里存放不同类型的元素(如 intstd::string 等)。
  • std::any 实现了 类型擦除,把任意类型的对象封装起来,统一接口访问。
3. any_iterator(Type-Erased Iterator)
  • 并非标准库,但很多库实现了 any_iterator
    • 可以统一不同类型容器的迭代器接口。
    • 例如:你可以通过 any_iterator 遍历 std::vector<int>std::list<int>,代码不需要知道容器类型。
4. std::function(函数封装器)
std::function<void()> f = [] { std::cout << "Hi\n"; };
  • std::function 是最经典的类型擦除工具之一。
  • 它可以封装:
    • 普通函数
    • 成员函数
    • lambda
    • 函数对象(functor)
  • 所有这些最终表现为同一种类型 —— std::function<Signature>

总结

应用使用场景背后原理
std::function封装任意 Callable类型擦除 + 虚调度
std::any存任意类型值类型擦除 + 类型标识
智能指针 Deleter自定义删除行为类型擦除
any_iterator通用迭代器抽象类型擦除
这些机制允许 C++ 写出更灵活、更泛化、更现代化的代码。

Practical Type Erasure(实际类型擦除) 的一些关键注意点潜在陷阱。下面我来逐条解释:

Type Erasure 并不是魔法(Not Magic)

虽然类型擦除看起来像魔法(你可以操作“未知类型”的对象),但实际上:

  • 背后是复杂的模板、虚函数、多态和堆分配等机制。
  • 最终必须有某一层知道具体类型,否则编译器没法生成正确代码。

Someone Must Know the Type(某个地方必须知道具体类型)

比如:

std::any a = 42;
std::cout << std::any_cast<int>(a);  // 必须告诉它是 int
  • std::any_cast<int> 就是告诉编译器类型的地方。
  • 如果你猜错类型,程序会抛出异常或崩溃。

Polymorphism Possible, but Ugly(可以实现多态,但写法可能很丑)

C++ 本身有传统的多态(虚函数 + 继承),但你也可以用类型擦除实现“伪多态”:

class Drawable {
public:
  template<typename T>
  Drawable(T t) : self(std::make_shared<model<T>>(std::move(t))) {}
  void draw() const { self->draw(); }
private:
  struct concept {
    virtual ~concept() = default;
    virtual void draw() const = 0;
  };
  template<typename T>
  struct model : concept {
    T data;
    model(T x) : data(std::move(x)) {}
    void draw() const override { data.draw(); }
  };

  std::shared_ptr<const concept> self;
};

这种代码:

  • 灵活、强大
  • 但结构复杂、不直观
  • 阅读和维护代价大

Use with Caution(谨慎使用)

类型擦除容易让代码:

  • 变得晦涩难懂(比如你不知道变量实际是什么类型)
  • 很难调试(错误信息模糊)
  • 可维护性差(新同事可能完全读不懂)
    使用场景:

非常合适:

  • 插件系统、脚本语言接口
  • 统一函数调用接口(如 std::function
  • 泛化配置系统(如使用 std::any
    不推荐:
  • 普通业务逻辑中滥用
  • 用来代替模板或继承时没有必要的情况

总结

含义
Not Magic背后仍然依赖 C++ 的机制(虚函数、模板、堆分配)
Someone Must Know最终必须知道类型,比如 any_cast、类型模板参数
Polymorphism Ugly虽然能实现“接口”,但写法较丑,易出错
Use with Caution若滥用,可能导致难维护的代码、类型不安全、调试困难等问题

这段内容是在描述一个面向客户端的架构设计理念,特别是在构建一个可扩展、可插拔的数据访问或服务接口时的策略。下面来逐句解释这些要点:

Architecture 架构设计核心思想

Client-facing Front End Interface

面向客户端的前端接口
这是指你定义一个统一的接口层(比如 DataProviderServiceRegistry 等),客户端只通过这个接口与系统交互。
客户端不关心后端具体怎么实现,只管发请求、拿结果。

Return any object or data by key

根据键返回任意对象或数据
这说明你的接口是基于键值(key-based)访问的,比如:

auto result = service.get("user_info");
  • 类似于动态配置、插件系统
  • 背后可能是 std::any、类型擦除、或工厂模式

Client decides on backend(s) to use

客户端决定使用哪些后端
这意味着:

  • 后端是可插拔的
  • 客户端可以选择使用哪个实现(甚至可以运行时选择)
    比如:
Backend* b = BackendFactory::get("SQLite");

或在构建配置中指定:

--use_backend=Redis

Compile only used back ends

只编译被使用的后端
为了减小编译时间和可执行文件体积:

  • 架构要允许按需编译
  • 可以用插件机制或 #ifdef 控制编译某些 backends:
#ifdef USE_REDIS_BACKEND
#include "redis_backend.hpp"
#endif

Client ignorant of implementation

客户端对实现细节一无所知
这强调了**“解耦”**:

  • 客户端只使用前端接口(如 get(key)
  • 客户端不需要知道使用的是 Redis、文件系统、数据库还是内存实现
  • 这让系统更灵活、更容易维护和替换后端

总结一句话:

这种架构允许客户端只关注“我要什么”,而不是“怎么给我”,通过键访问抽象对象,并允许只编译和加载需要的后端实现。

后端(Back end)架构的设计要点,通常用来说明系统中后端模块的职责和特性。详细理解如下:

Architecture — Back end(架构设计 —— 后端)

Extendible(可扩展)

后端应具有良好的扩展性,能够支持新增功能或类型。

  • 后端设计要支持动态增加新的功能模块或支持更多数据类型
  • 例如支持新增数据库驱动、文件格式或消息协议时不需改动核心代码

Supports specified types(支持指定类型)

后端能处理系统规定的数据类型或对象类型。

  • 明确后端处理的数据范围(如字符串、整数、结构体、复杂对象等)
  • 例如数据库后端支持存储和查询的字段类型,文件后端支持的序列化格式等

Instances created and destroyed(支持实例的创建和销毁)

后端允许动态创建和销毁其实例。

  • 后端并非单例,而是能同时存在多个独立实例
  • 这些实例可以被创建、配置、销毁,支持生命周期管理
  • 便于多任务或多用户场景,互不干扰

Multiple instances supported(支持多个实例)

同时允许存在多个后端实例。

  • 比如一个程序可能同时连接多个数据库,或多个缓存系统实例
  • 每个实例有自己独立状态和配置
  • 支持负载均衡、分布式等高级用例

总结:

后端设计要做到灵活可扩展,能处理多种数据类型,支持多个实例动态管理,这保证了系统的模块化、复用性以及满足复杂场景需求。

class CAnyProperty
{
public:
    // 智能指针类型,指向所有处理器基类,支持多态
    typedef std::shared_ptr<CAnyHandlerBase> THandlerPtr;

    // 模板方法,根据key获取指定类型T的值
    template<typename T>
    T Get(const std::string & key) const;

    // 模板方法,根据key设置指定类型T的值
    template<typename T>
    void Set(const std::string & key, const T & value);

    // 添加一个获取处理器,所有与其对应类型相关的Get调用会使用它
    void AddGetHandler(THandlerPtr handler_ptr);

    // 模板方法,为类型T设置对应的Set处理器
    template<typename T>
    void SetSetHandler(THandlerPtr handler_ptr);

private:
    // 类型信息到获取处理器集合的映射,支持一个类型多个获取处理器
    typedef std::map<Loki::TypeInfo, std::vector<THandlerPtr>> TGetHandlerMap;
    TGetHandlerMap m_GetHandlerMap;

    // 类型信息到设置处理器的映射,一个类型对应一个设置处理器
    typedef std::map<Loki::TypeInfo, THandlerPtr> TSetHandlerMap;
    TSetHandlerMap m_SetHandlerMap;
};

这段代码描述了一个面向客户端的通用属性访问接口CAnyProperty类),它通过“类型擦除”和“多态”机制,支持对任意类型的属性进行获取(Get)设置(Set),并且可以注册不同类型的处理器(Handler)来定制具体的访问逻辑。

主要成员和设计思路

  • 模板方法 Get 和 Set

    • template<typename T> T Get(const std::string &key) const
      通过字符串 key 获取对应的类型为 T 的属性值。
    • template<typename T> void Set(const std::string &key, const T &value)
      通过 key 设置类型为 T 的属性值。
  • 处理器 Handler(策略模式)

    • 支持注册“获取”处理器:AddGetHandler(THandlerPtr handler_ptr)
    • 支持注册“设置”处理器:template<typename T> void SetSetHandler(THandlerPtr handler_ptr)
    • 处理器是指派给特定类型处理对应Get或Set逻辑的类,使用基类指针CAnyHandlerBase进行抽象。
  • 两个映射表维护不同类型的处理器

    • m_GetHandlerMap:以类型信息(Loki::TypeInfo)为键,存储一组对应的获取处理器。
      • 一个类型可能有多个获取处理器。
    • m_SetHandlerMap:以类型信息为键,存储一个对应的设置处理器。
      • 一个类型仅对应一个设置处理器。

设计目的和用法

  • 允许客户端通过统一的接口动态获取或设置不同类型的数据,而不必在编译期确定类型。
  • 通过注册不同的“处理器”,可以灵活定义某个类型数据的获取和设置行为。
  • Loki::TypeInfo 用于运行时区分类型,实现类型安全的类型擦除。

总结

CAnyProperty 是一个客户端暴露的通用属性接口,支持基于类型擦除的多类型访问,并允许通过处理器机制定制不同类型属性的获取和设置逻辑,实现了灵活且可扩展的设计,满足“客户端只关注接口、背后多后端实现”的需求。

这个类是“后端处理器”的基类,提供统一的接口供前端调用,同时支持多态,方便不同具体后端实现自己的行为。

详细理解和注释如下:

class CAnyHandlerBase
{
public:
    CAnyHandlerBase() {}  // 默认构造函数
    virtual ~CAnyHandlerBase() {}  // 虚析构,支持多态正确销毁

    // 虚函数,获取指定key对应的属性值,返回boost::any类型(可容纳任意类型)
    // 默认实现是抛出异常,表示该后端不支持Get操作
    virtual boost::any Get(const std::string & /*key*/) const
    {
        throw CAnyPropertyException(CAnyPropertyException::eNoGet);
        return boost::any(); // 不会执行到这里
    }

    // 虚函数,设置指定key对应的属性值,参数为boost::any类型(任意类型)
    // 默认实现是抛出异常,表示该后端不支持Set操作
    virtual void Set(const std::string & key, const boost::any & /*value*/)
    {
        throw CAnyPropertyException(CAnyPropertyException::eNoSet);
    }

    // 纯虚函数,返回后端名称,用于错误报告和调试
    virtual std::string Name() const = 0;

    // 纯虚函数,返回该后端支持处理的类型列表(用Loki::TypeInfo表示)
    virtual std::vector<Loki::TypeInfo> GetHandledTypes() const = 0;
};

核心点理解:

  • 多态接口:前端通过基类指针或智能指针调用不同具体后端的Get/Set。
  • 异常机制:默认实现抛异常,强制后端显式实现支持的操作。
  • 类型信息:通过GetHandledTypes()告知前端哪些类型它可以处理。
  • 后端名字Name()方法用于诊断错误,方便定位问题。

这个 CAnyProperty 类是“客户端接口”,提供统一的 GetSet 接口让客户端用键(key)访问任意类型的数据。内部实际调用了私有的通用接口(比如 x_GetAnyx_SetAny),把类型擦除到 boost::any,实现对任意类型的支持。

详细理解:

class CAnyProperty
{
public:
    // 获取指定key对应的值,模板参数T表示期望的类型
    // 内部调用x_GetAny返回boost::any,再用boost::any_cast转换成T类型
    template<typename T>
    T Get(const std::string& key) const
    {
        return boost::any_cast<T>(x_GetAny(key, typeid(T)));
    }

    // 设置指定key对应的值,模板参数T表示传入值的类型
    // 内部调用x_SetAny进行存储
    template<typename T>
    void Set(const std::string& key, const T& value)
    {
        x_SetAny(key, value);
    }

private:
    // x_GetAny 和 x_SetAny 是该类的私有成员函数,负责底层操作
    // 这里没有给出定义,实际会调度到相应后端实现
    boost::any x_GetAny(const std::string& key, const std::type_info& type) const;
    void x_SetAny(const std::string& key, const boost::any& value);
};

核心点

  • 模板接口:使用模板参数 T 支持任意类型的获取和设置,调用方便且类型安全。
  • 类型擦除:底层用 boost::any 作为通用存储格式,把类型信息封装隐藏起来。
  • 类型检查boost::any_cast 负责在运行时检查类型是否匹配,防止错误使用。
  • 键值访问:通过字符串键索引数据,支持类似字典或属性集的访问方式。
  • 底层细节隐藏:客户端代码无须知道数据是如何存储和管理的,只关注 GetSet 的类型接口。

这个 CAnyProperty::x_GetAny 函数是 “Glue Getter”(粘合器读取函数),它连接前端接口和后端实现,负责根据类型和键名查找对应的处理器(handler),从后端获取对应的数据并返回。

详细分析

boost::any
CAnyProperty::x_GetAny(const std::string& key, const Loki::TypeInfo& value_type) const
{
    // 如果key为空,抛异常
    if (key.empty()) throw CAnyPropertyException(CAnyPropertyException::eEmptyKey);

    // 根据value_type(类型信息)去查找对应的“Get处理器列表”
    TGetHandlerMap::const_iterator handler_list_iter = m_GetHandlerMap.find(value_type);

    // 如果没有对应类型的处理器,抛异常,提示无读取处理器
    if (m_GetHandlerMap.end() == handler_list_iter) {
        throw CAnyPropertyException(CAnyPropertyException::eNoReadHandler, value_type.name());
    }

    // 取出该类型对应的处理器列表(可能多个)
    const TGetHandlerMap::mapped_type& handler_list = handler_list_iter->second;

    // 在处理器列表中执行条件搜索(比如按key匹配),找到对应的处理器
    CQueryHandler a_query_handler =
        for_each_if(handler_list.begin(), handler_list.end(), CQueryHandler(key));

    // 如果没找到对应key,抛异常
    if (a_query_handler.GetValue().empty()) {
        throw CAnyPropertyNoKeyException(eKeyNotFound, key);
    }

    // 获取处理器查询到的值,类型是boost::any
    boost::any a = a_query_handler.GetValue();

    // 返回该值
    return a;
}

关键点

  • 参数说明

    • key:字符串类型的键,用于查找对应数据
    • value_type:通过 Loki::TypeInfo 表示的目标数据类型(类型信息)
  • m_GetHandlerMap
    这是一个从类型信息映射到一组“读取处理器”(getter handlers)的映射表。每种类型可能对应多个处理器。

  • for_each_if
    是一个自定义的算法,类似 std::find_if,在多个处理器中找到“能处理当前key”的那个。

  • CQueryHandler
    是一个封装了查找和获取值的辅助类,用来匹配key并提取结果。

  • 异常处理
    代码里严格检查了各种异常情况:

    • 空key
    • 类型无处理器
    • key无对应值
  • 返回值
    返回一个 boost::any,包含了对应的值,保持了类型擦除,方便客户端通过 boost::any_cast 转换成对应类型。

总结

这个函数是“桥梁”,负责从指定类型的所有getter处理器里,找到合适的处理器查询对应key的数据,统一返回结果。它实现了类型和键的双重查找逻辑,是整个“多后端多类型”属性系统的核心读操作。

这段代码展示了一个基于类型擦除的属性访问系统中,“读写操作”和“处理器注册”部分的核心机制。结合之前的CAnyProperty类,可以理解为“前端接口”与“后端处理器”之间的桥梁代码。

1. for_each_if 函数模板

template<typename InputIterator, typename Function>
Function for_each_if(InputIterator first, InputIterator last, Function f)
{
    for (; first != last; ++first)
        if (f(*first))    
            return f;
    break; // 注意这里实际应是 return f; 或者循环结束后的return,break写法不正确
}
  • 功能:遍历 [first, last) 范围内的元素,调用 f(*first),如果返回true,则立即返回函数对象f
  • 这是自定义的条件遍历器,用于查找满足某条件的第一个元素,并返回执行状态的函数对象。

2. CQueryHandler 类 — 读操作谓词

class CQueryHandler : public std::unary_function<CAnyProperty::THandlerPtr, bool>
{
public:
    CQueryHandler(const std::string& key) : m_Key(key) {}

    boost::any GetValue() const { return m_Value; }

    // 调用operator(),用handler_ptr去尝试读取key对应的值
    bool operator()(CAnyProperty::THandlerPtr handler_ptr)
    {
        assert(m_Value.empty());
        m_Value = handler_ptr->Get(m_Key);
        return !m_Value.empty();
    }

private:
    std::string m_Key;
    boost::any m_Value;
};
  • 这是一个函数对象,用于对每个handler_ptr调用Get(key),尝试读取数据。
  • 如果读到了非空数据(即存在有效值),返回true,否则false
  • 成功读取的数据会保存到m_Value,供后续访问。

3. x_SetAny 函数 — 写操作桥接函数

void CAnyProperty::x_SetAny(const std::string& key, const boost::any& value)
{
    if (key.empty()) {
        throw CAnyPropertyException(CAnyPropertyException::eEmptyKey);
    }
    Loki::TypeInfo value_type(value.type());
    TSetHandlerMap::iterator handler_iter = m_SetHandlerMap.find(value_type);
    if (handler_iter == m_SetHandlerMap.end()) {
        throw CAnyPropertyException(CAnyPropertyException::eNoWriteHandler, value_type.name());
    }
    THandlerPtr handler_ptr = handler_iter->second;
    assert(handler_ptr);
    handler_ptr->Set(key, value);
}
  • 根据传入的value的类型,从写处理器映射表中找到对应的写处理器。
  • 找不到写处理器会抛异常。
  • 找到后调用对应写处理器的Set(key, value)完成写操作。

4. 添加处理器相关接口

inline void CAnyProperty::AddGetHandler(CAnyProperty::THandlerPtr handler_ptr)
{
    std::vector<Loki::TypeInfo> handled_types = handler_ptr->GetHandledTypes();
    for (auto type_iter : handled_types) {
        TGetHandlerMap::mapped_type& handler_list = m_GetHandlerMap[type_iter];
        handler_list.push_back(handler_ptr);
    }
}

template<typename T> 
void CAnyProperty::SetSetHandler(THandlerPtr handler_ptr)
{
    m_SetHandlerMap[Loki::TypeInfo(typeid(T))] = handler_ptr;
}
  • AddGetHandler:把一个读处理器注册到对应的多个类型映射里,因为一个处理器可能支持多种类型。
  • SetSetHandler:给某个具体类型注册写处理器,写处理器一对一绑定类型。

总结

  • 读操作:通过类型找到对应的多个读处理器,遍历调用Get(key),找到第一个能返回非空值的处理器并返回结果。
  • 写操作:根据值类型找到唯一写处理器,调用Set(key, value)完成写入。
  • 处理器管理:支持动态注册读写处理器,形成扩展性强的插件式架构。
  • 设计目的:实现面向客户端的灵活接口,背后可以支持多种类型和多种数据后端,解耦类型与具体实现,方便扩展和维护。

这段代码展示了 后端处理器(Backend Handlers) 的两个实现例子,配合前面你提到的基类CAnyHandlerBase,说明了如何实现不同类型和不同存储介质的“读写”逻辑。

1. CAnyHandlerBase(基类)

  • 职责:定义接口,后端处理器都继承它,实现具体的GetSet、类型支持等方法。
  • GetSet默认抛异常,说明子类必须覆盖,否则不支持对应操作。
  • Name()用于返回处理器名称(调试、错误信息用)。
  • GetHandledTypes()返回此处理器支持的类型列表。

2. CAnyPropertyHandlerMemory<TValue>(简单内存存储处理器)

这是一个模板类,支持任意类型TValue,通过内部std::map<std::string, TValue>来存储键值对。

template <typename TValue>
class CAnyPropertyHandlerMemory : public CAnyHandlerBase
{
public:
    virtual boost::any Get(const std::string& key) const override
    {
        boost::any value;
        auto it = m_Map.find(key);
        if (it != m_Map.end()) {
            value = it->second;
        }
        return value;
    }

    virtual void Set(const std::string& key, const boost::any& value) override
    {
        m_Map[key] = boost::any_cast<TValue>(value);
    }

    virtual std::vector<Loki::TypeInfo> GetHandledTypes() const override
    {
        return CreateTypeVector<TValue>()();
    }

private:
    std::map<std::string, TValue> m_Map;
};
  • 功能

    • Get:查找m_Map,返回对应的值(boost::any包装)。
    • Set:将boost::any转换成TValue存入m_Map
  • 适用场景:内存临时存储,适合简单、快速的数据保存。

  • CreateTypeVector<TValue>()()是辅助函数,用于返回TValue的类型信息列表,告诉系统该处理器能处理哪些类型。

3. CAnyHandlerEnv(环境变量处理器)

boost::any CAnyHandlerEnv::Get(const std::string& key) const
{
    boost::any value;
    char* env_value = ::getenv(key.c_str());
    if (env_value) {
        value = std::string(env_value);
    }
    return value;
}

void CAnyHandlerEnv::Set(const std::string& key, const boost::any& value)
{
    std::string env_value = key + "=" + boost::any_cast<std::string>(value);
    int putenvReturn = ::putenv(const_cast<char*>(env_value.c_str()));
    if (putenvReturn) {
        throw CAnyPropertyException(...);
    }
}

std::vector<Loki::TypeInfo> CAnyHandlerEnv::GetHandledTypes() const
{
    return CreateTypeVector<std::string>()();
}
  • 功能
    • 通过系统API访问环境变量,Get时使用getenv,返回字符串环境变量值。
    • Set时,调用putenv设置环境变量,传入格式是"key=value"
  • 类型:仅支持std::string类型的读写。
  • 异常处理putenv失败时抛异常,保证调用者能感知错误。

总结

  • 这两个类是CAnyHandlerBase的具体实现,分别演示了:
    • 内存存储后端(任意类型支持,基于模板实现)
    • 环境变量后端(只支持字符串类型)
  • 它们共同体现了后端的职责:
    • 提供具体数据存取逻辑
    • 声明自己支持的类型
  • 与前端CAnyProperty结合,可以实现统一接口,背后多后端切换,灵活且扩展性强。

这段内容展示了两个更复杂的 后端处理器(Backend Handlers) 示例,扩展了前面的简单内存和环境变量处理器,说明了如何处理复杂数据结构和实际业务需求。

1. 后端 JSON 处理器 — CAnyHandlerJSON

  • 场景:处理 JSON 格式数据,将 JSON 文件中的内容加载进内存,方便统一访问和操作。
  • 结构示例(JSON):
{
  "firstName": "Homer",
  "lastName": "Simpson",
  "age": 38,
  "address": {
    "streetAddress": "742 Evergreen Terrace",
    "city": "Springfield",
    "state": "OR",
    "postalCode": "96522"
  },
  "phoneNumber": [
    { "type": "home", "number": "939 555-1234" },
    { "type": "fax", "number": "636 555-4567" }
  ]
}
  • 实现思路
class CAnyHandlerJSON : public CAnyHandlerBase
{
    // 存储键值对,值用 boost::any 支持多种类型
    std::map<std::string, boost::any> m_values;

public:
    CAnyHandlerJSON(const std::string& jsonFileName)
    {
        // 解析 JSON 文件,假设 topObject 是解析后的根 JSON 对象
        m_values["firstName"] = topObject["firstName"].get_value<std::string>();
        m_values["lastName"] = topObject["lastName"].get_value<std::string>();
        m_values["age"] = topObject["age"].get_value<int>();

        // 对嵌套的地址字段,先转换成 map<string,string>
        std::map<std::string, std::string> addressMap;
        // 解析 address 子对象,赋值到 addressMap
        // ...
        m_values["address"] = addressMap;

        // 对电话字段,转换成包含结构体(如 SPhoneNumber)的 vector
        std::vector<SPhoneNumber> phoneVector;
        // 解析 phoneNumber 数组,填充 phoneVector
        // ...
        m_values["phone"] = phoneVector;
    }

    // 其他必要接口(Get、Set、Name、GetHandledTypes)未展示
};
  • 特点

    • 支持复杂数据结构(嵌套对象、数组)
    • 内部用 boost::any 可存储多种类型,包括自定义结构体、容器等
    • 适合配置文件、数据交换格式的解析和访问

2. 真实业务后端示例 — CGPAttrHandlerBuildrunID

  • 场景:与数据库交互,获取或设置具体业务数据(例如构建运行ID)
  • 类设计
class IConnection; // 抽象数据库连接接口
class CGPAttrHandlerBuildrunID : public CGPAttrHandlerBase
{
public:
    virtual boost::any Get(const std::string& key) const override;
    virtual std::string Name() const override;
    virtual std::vector<Loki::TypeInfo> GetHandledTypes() const override;

    CGPAttrHandlerBuildrunID();

private:
    void x_ConnectToDatabase();
    int x_GetBuildID() const;
    std::string x_ConstructSQL() const;

    CGPipeProperty m_Environment;     // 可能是环境配置或上下文对象
    std::string m_Database;
    std::string m_Username;
    std::string m_Password;
    std::auto_ptr<IConnection> m_Connection; // 负责数据库连接生命周期管理
};
  • 设计要点
    • 实现数据库连接、SQL构造、数据获取的私有函数
    • 通过 Get 函数执行查询返回数据(包装成 boost::any
    • 使用智能指针管理数据库连接资源
    • 支持多个后端类型和复杂逻辑(可能异步、事务等)

总结

  • JSON Handler 用于支持结构化、层次化数据的存取,适合配置文件或数据交换。
  • 数据库 Handler 体现了“后端”灵活可扩展的特性,可以封装复杂的业务逻辑和资源管理。
  • 这两个例子都继承了相同基类,实现了统一接口,方便前端透明访问不同数据源。
  • 设计思想体现“解耦”“统一接口”“多态扩展”。

“Practical Type Erasure”(实用类型擦除)设计的核心要点:

1. No Magic Bullet – Someone will have to cast

“没有万能灵药——总得有人来做类型转换。”
虽然类型擦除技术让我们可以在接口层隐藏具体类型,实现更灵活的多态,但底层还是要进行类型转换(cast)。这需要程序员明确知道什么时候以及如何进行安全的转换,避免类型错误。

2. Helps Expose Clean Interfaces – Even when internals are dirty

“即使内部复杂,也能暴露干净的接口。”
类型擦除允许封装复杂且多样的实现细节,前端接口保持简洁统一,用户无需关心内部如何实现。它提升了模块化和接口设计的清晰度。

3. Glues OO and Generic Code

“将面向对象和泛型代码粘合在一起。”
类型擦除是连接传统面向对象编程(多态、虚函数等)和现代泛型编程(模板、类型参数化等)的桥梁。它让你用统一的方式处理各种不同类型的对象,而无需在代码中显式指定所有类型。

总体理解

类型擦除不是魔法,也不会自动解决所有问题,但它是构建灵活、模块化、高度抽象系统的重要技术。它帮助你:

  • 提供整洁的API和接口;
  • 隐藏内部复杂细节;
  • 结合面向对象和模板泛型的优势。
    不过,需要谨慎使用,确保类型转换安全和代码可维护。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值