欢迎光临
我们一直在努力

深入解析享元模式:一个C++模板实现的多态数据缓存库

个人专著《C++元编程与通用设计模式实现》由清华大学出版社出版。该书内容源于工业级项目实践,出版后市场反馈积极(已加印)。其专业价值获得了图书馆系统的广泛认可:不仅被中国国家图书馆作为流通与保存本收藏,还被近半数省级公共图书馆及清华大学、浙江大学等超过35所高校图书馆收录为馆藏。

个人软仓,gitee搜索“galaxy_0”

深入解析享元模式:一个C++模板实现的多态数据缓存库

1. 享元模式简介

在大型软件系统中,我们经常需要创建大量细粒度的对象,比如游戏中的粒子、文档中的字符、数据库连接等。如果每个对象都独立存在,内存开销将非常可观。享元模式(Flyweight Pattern) 就是为了解决这个问题而生的——它通过共享技术来高效地支持大量细粒度对象的复用。就像图书馆里的书,不同的人借阅同一本书,但书本身只有一本,大家共享。

今天我们要介绍的是一个基于C++模板实现的通用享元类库。它能够以键值对的形式存储任意类型的对象,并通过编译期开关提供两种实现方式:一种使用自定义的variant进行类型擦除,另一种通过继承和多态实现。无论哪种方式,都保证了线程安全,并提供了简洁的接口。让我们一起来看看这个“数据共享池”是如何设计的吧!

2. 代码设计解析

2.1 整体设计思路

这个享元类的核心思想是:用一个std::map将键与数据对象关联起来,通过键来共享和访问数据。它的设计亮点在于:

  • 两种模式可选:通过宏FLYWEIGHT_USE_VARIANT在编译期切换实现方式。使用variant时,数据以类型擦除的形式存储,更加灵活;不使用variant时,通过基类指针和继承实现多态。
  • 线程安全:内部使用std::mutex保护所有操作,确保在多线程环境下安全访问。
  • 类型安全:通过模板参数和静态断言,确保操作的数据类型符合预期。
  • 统一接口:无论哪种模式,都提供set、get、has、erase等方法,使用体验一致。

为了实现这些功能,它设计了几个关键组件:

  • private__::dataItfc__(非variant模式):一个空基类,用于实现类型擦除。
  • dataItfc(非variant模式):派生自上述基类的接口模板,定义了set和get纯虚函数,供具体数据类实现。
  • wheels::variant(variant模式):一个自定义的变体类型,能够存储任意类型的数据(类似于std::any或std::variant)。
  • flyweight主类:模板参数为键类型idType,内部持有一个std::map和一个互斥锁。

2.2 编译期开关与类型定义

代码开头通过宏控制实现方式:

#if !defined( FLYWEIGHT_USE_VARIANT )
# define FLYWEIGHT_USE_VARIANT (1)
#endif

默认启用variant模式。在variant模式下,使用wheels::variant存储数据;否则,需要自定义数据接口。

namespace private__ {


struct dataItfc__ {


virtual ~dataItfc__() {

}
};
}

这个空基类的作用是作为所有可存储类型的公共基类,从而实现类型擦除——我们可以用基类指针指向不同的派生类。

2.3 非variant模式下的数据接口

如果不使用variant,用户需要让自己的数据类继承自dataItfc模板:

template< typename dataType >
struct dataItfc : public private__::dataItfc__ {


virtual void set( const dataType& d ) = 0;
virtual const dataType& get() const = 0;
};

这个接口规定了数据类必须实现set和get方法,从而保证可以通过基类指针操作具体数据。这是一种经典的“多态类型擦除”手法。

2.4 享元主类:flyweight

template< typename idType >
class flyweight
{


protected:
mutable std::mutex m_mutex__;
#if FLYWEIGHT_USE_VARIANT == 1
std::map< idType , wheels::variant > m_itfcs__;
#else
std::map< idType , private__::dataItfc__ * > m_itfcs__;
#endif
public:
#if FLYWEIGHT_USE_VARIANT == 1
using iterator = typename std::map< idType , wheels::variant >::iterator;
#else
using iterator = typename std::map< idType , private__::dataItfc__ * >::iterator;
#endif
// … 方法 …
};

  • m_mutex__:保护所有操作的互斥锁,确保线程安全。
  • m_itfcs__:核心存储结构,是一个从键到数据的映射。在variant模式下,值是wheels::variant,可以存储任意类型;在非variant模式下,值是基类指针,指向实际数据对象。
  • 迭代器类型:根据模式对应定义,方便外部遍历。

2.5 核心方法解析

2.5.1 构造与析构

flyweight() {

}
virtual ~flyweight() {


#if !FLYWEIGHT_USE_VARIANT
for( auto item : m_itfcs__ ) {


delete item.second;
}
#endif
}

析构时,在非variant模式下需要手动delete所有存储的指针,因为容器中存的是原始指针。variant模式下,wheels::variant会自动管理内存,无需手动释放。

2.5.2 has方法

bool has( const idType& id ) {


std::lock_guard< std::mutex> lock( m_mutex__ );
auto it = m_itfcs__.find( id );
return (it != m_itfcs__.end() );
}

检查是否存在指定键,线程安全。

2.5.3 variant模式下的set和get

set方法(variant模式):

template< typename T >
bool set( const idType& id , T&& data ) {


auto var = wheels::variant::make( std::forward< T >( data ));
std::lock_guard< std::mutex> lock( m_mutex__ );
auto it = m_itfcs__.find( id );
if( it != m_itfcs__.end() ) {


it->second = std::move( var );
} else {


auto rc = m_itfcs__.insert( std::make_pair( id , var ) );
return rc.second;
}
return true;
}

  • 通过wheels::variant::make将传入的数据转换为一个variant对象。
  • 如果键已存在,则替换其值;如果不存在,则插入新键值对。
  • 使用std::lock_guard保证原子操作。

get方法(variant模式):

variant& get( const idType& name ) {


std::lock_guard< std::mutex> lock( m_mutex__ );
auto it = m_itfcs__.find( name );
if( it == m_itfcs__.end() ) {


throw std::runtime_error( \”找不到指定变量\” );
}
return it->second;
}

  • 查找指定键,如果不存在则抛出异常。
  • 返回variant的引用,调用者可以通过variant的接口(如get<T>())提取具体数据。

operator[](variant模式):

wheels::variant& operator[](const idType& name) {


auto it = m_itfcs__.find( name );
if( it == m_itfcs__.end() ) {


throw std::runtime_error( \”找不到指定变量\” );
}
return it->second;
}

提供了类似数组的访问语法,但注意它不进行插入操作,只用于已存在键的访问。

2.5.4 非variant模式下的set和get

非variant模式下的set方法较为复杂,因为它需要处理类型检查和对象创建:

template< typename T , typename ...PARAMS >
bool set( const idType& id , PARAMS&&... args ) {


bool ret = true;
static_assert( std::is_base_of< private__ :: dataItfc__ , T>::value , \”\” );
static_assert( ( std::is_class< T >::value && std::is_default_constructible< T >::value ) || std::is_arithmetic<T>::value , \”\” );
auto it = m_itfcs__.find( id );
if( it != m_itfcs__.end() ) {


auto p = dynamic_cast< dataItfc<PARAMS...> * >( it->second );
p->set( std::forward<PARAMS>(args)... );
} else {


try {


auto * t = new T;
m_itfcs__.insert( std::make_pair( id , t ) );
t->set( std::forward<PARAMS>(args)... );
} catch( std

赞(0)
未经允许不得转载:171主机测评 » 深入解析享元模式:一个C++模板实现的多态数据缓存库
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址