复合对象的基于策略的引用计数实现






4.77/5 (12投票s)
各种类型的引用计数智能指针和句柄。
目录
引言
本文是对先前在 CodeProject 上的文章“东方最安全的智能指针”和“不同编码方法的比较”的自然演进。
这个主题在许多面向对象和泛型编程书籍中都有广泛的讨论,并提供了各种解决方案。但是,根据我的经验,我经常在某些“过于固执”的实现中遇到麻烦。
你们中的许多人可能熟悉 Boost 或 Loki 等库。
好吧,如果您觉得它们的智能指针实现令您满意……那么请忽略本文!即使它能给您带来一些新的东西,您可能已经有足够的理由继续使用它们,并且会想为什么还要发明另一个轮子。
但是,如果您认为代码有时是过度设计的,并且您需要一些更小巧的东西,或者您需要一些更简单的东西来扩展,或者您只是想了解一种可能的策略化设计方式,那么您可以从中获得一些东西。
由于我对这类东西并不陌生,而且一些读者可能也知道我关于同一主题的其他文章,我必须警告您:这不是前一系列文章的更新,而是用一个新引擎重新开始,这个引擎“更现代”,并且不像以前那样倾向于保留匈牙利命名法或其他固执的模式实现。
这次我不会遵守任何“宗教”,而是逐个审视它们,目标只有一个:最小化所需的代码。
代码结构
由于许多类是模板,我采用了“包含模型”:所有代码都包含在头文件中,您可以将它们包含到您的项目中。头文件的依赖关系在头文件内部解决,不提供“cpp”文件。
所有函数和类都分组在命名空间中,所有命名空间都分组在 GE_
命名空间下。这样做的想法是,这些符号不应与您可能使用的任何其他第三方库发生冲突。如果发生冲突,只需将 GE_
字符串替换为我提供的所有文件中的相应字符串。
不存在对 MFC、ATL 或 WTL 的依赖。相反,我依赖 C++ STL 和一些 CRT 头文件。所有这些依赖关系都通过“stdx/dependency.h”文件包含在所有文件中。
该文件还包括一些广泛用于其他所有文件的“助手”头文件。它不包含“stdx”项目本身的头文件。
为了避免“标准”语言类与 Windows 相关代码之间的混淆,“stdx”实用项目将所有与 Windows 无关的代码独立于“windows.h”进行编码和分组。相反,所有围绕 Windows 功能的 C++ 包装器都将被包装在“winx”实用项目中(不在此文章中)。
测试单元位于相应的 C++ 项目中,命名为 C0、C1……Cx。在发布版本中,不要在调试器之外运行它们,因为它们没有任何效果。它们在调试模式下、在调试器中运行才有意义,因为它们会在输出窗口中“跟踪”。单步执行也是了解它们如何工作的好方法。
使用的模式
最常用的模式是引用计数,最常用的设计模式是策略继承。并非因为我相信这些模式能够解决一切问题,而是因为我发现它们在处理动态分配和有限资源时特别有用。
代码使用 C++ 7.0 编译。它也应该能很好地用于 C++ 7.1 甚至 8.0(原生),除了偶尔忘记输入的 typename
关键字。
通过引用计数,我主要指的是在对象生命周期的特定“时刻”应用预定义的动作,例如第一次引用时、每次新引用时、每次引用丢失时以及没有引用时。
通过策略继承,我指的是通过模板参数提供的基类来跨函数实现给定类的操作。通常是这样的:
template<class base> class myclass: public base { ... };
由于这可能是递归的,您可以组合策略,如下所示:
typedef aclass<apolicy<anotherpolicy<yetanother<aType> > > > atypeinstance;
当需要对象的多态性时,策略设计则通过递归模板实现:
template<class Derived, class traits> class myclass { protected: virtual ~myclass() {} Derived* pThis() const { return static_cast<Derived*>(const_cast<my_class*>(this)); } static myclass* P(Derived* p) { return static_cast<myclass*>(p); } ... //other functions (also virtual) };
本质上,Derived
是一个以 myclass<Derived>
作为其基类的类,而它本身可以作为各种覆盖其接口的类型的(通用)基类。
其他模式,如“组合-访问者”或“信号-事件”等,主要是以非传统的方式在这些模式之上实现的,将策略设计与 RTTI OOP 继承(虚函数、虚基类等)混合使用。
实现引用计数
经典的引用计数实现就是“智能指针”所附带的,它们有各种形式,如 boost 或 loki 的。
但是,处理 Win32 有些不同:引用计数不仅需要销毁策略,还需要不同的数据存储方式(关闭 HANDLE
可能需要比 HANDLE
本身更多的信息)、不同的创建和获取策略,能够处理“指针”和“值”语义,支持“强”和“弱”引用等等。
就我的经验而言,“策略继承”提供了处理这些变体模块化所需的灵活性。
std::smart<.> 类
这是开始的地方,用于拥有智能指针、智能句柄、“智能任何东西”。抛开市场营销不谈,它应该尽可能“空”,只实现无法继承的东西(即:复制和赋值),以继承自参数化基类的函数形式。
//class smart: inherit from a policy. // requires: // assign, alias_type, value_type, acquire_type // provides // copy, assignment, conversion operators template<class smart_policy> class smart: public smart_policy, public operators<smart> { public: smart() {} ~smart() {} smart(const smart& s) { assign(static_cast<const smart_policy&>(s)); } template<class other> smart(const smart<other>& s) { assign(static_cast<const other&>(s)); } template<class A, class B> smart(const A& a, const B& b) { assign(a,b); } //dumb-to-smart is potentially unsafe for policy //requiring extra data to be recorded // see pointers::mappedpolicy or pointers::intrpolicy // for safe assignments. smart(typename smart_policy::acquire_type a) { assign(a); } smart& operator=(const smart& s) { assign(static_cast<const smart_policy&>(s)); return *this; } typedef typename smart_policy::alias_type alias_type; typedef typename smart_policy::acquire_type acquire_type; typedef typename smart_policy::value_type value_type; };
请注意,smart
不包含任何数据或数据操作。它假定所有内容都已继承。它唯一的作用是调用 assign
,而 assign
应该由 smart_policy
在所有必需的变体中继承。类型定义也被视为继承的。支持从其他类型的 smart
或从 acquire_type
进行转换。
refcounts::policy<.>
引用计数实现在 stdx::refcounts
命名空间中,通过 stdx::refcounts::policy
类,该类处理引用计数器(包括强引用和弱引用),并将数据存储及其赋值委托给其参数化基类。
strong
意味着引用控制其持有的对象(例如,当没有“控制器”控制它时将其删除),weak
意味着引用“观察”持有的对象,并在对象不再存在时报告无效状态,但它本身不“管理”对象。
它还提供了一个公共函数,由于在 smart
中的继承,该函数非常容易使用,即 operator!()
,当没有智能引用对引用的计数器处于活动状态(或……没有对象)时,或者当继承的策略有自己的 operator!()
返回 true 时,该函数为 true。
类似地,如果引用的计数器对象相同,operator==
和 operator<
会短路继承的函数:当这种情况发生时,指针本身就是相等的。
实际上,它将 assign
函数转换为对共享计数器的增量/减量,并调用其他“由模板参数继承”的函数,如 on_addref
、on_lastrelease
等,如下面的 assign
变体所示:
template<class other, bool b> void assign(const policy<other,b>& a) { if(a.pCnt == pCnt) return; clear_(); refcnt_policy::assign(static_cast<const other&>(a)); set_(a.pCnt); } void assign(acquire_type a) { counters* p = get_counters(a); if(pCnt == p) return; clear_(); refcnt_policy::assign(a); set_(p); }
pCnt
是一个指向 counters
struct
的指针。get_couters
函数必须通过 policy
模板类(策略的策略)的 Type 参数继承,这使得我们能够将一个普通的指针赋给一个智能指针。稍后我们将看到如何做到这一点。
内部的 clear_
和 set_
函数是计数机制的关键,它们分别调用 pCnt->increment
和 pCnt->decrement
。
increment
和 decrement
存在于 counters
中,如下所示:
template<class P> void decrement(P& p, bool bStrong) { if(bStrong) { p.on_release(); if(hold == 1) { p.on_lastrelease(); p.on_delete(); } hold--; } ref--; if(!ref) delete this; } template<class P> void increment(P& p, bool bStrong) { ref++; if(bStrong) { hold++; if(hold==1) p.on_firstref(); p.on_addref(); } }
现在问题变成了“为 refcounts::policy
提供所需的 on_xxx
函数以及 get_counters
函数”。
指针语义
为了提供“指针语义”,我们需要一个策略来存储指针并提供解引用和成员访问运算符。
最通用的是 stdx::pointers::policy<Type>
。
template<class Type> class policy { friend class policy; private: Type* ptr; protected: typedef Type* alias_type; typedef Type value_type; policy() { ptr=0; } void on_addref() {} void on_firstref() { } void on_release() {} void on_lastrelease() { } void on_delete() { if(ptr) delete ptr; ptr=0; } refcounts::counters* get_counters(Type* p) { return (p)? new refcounts::counters: 0; } void clear() { ptr = 0; } template<class other> void assign(const policy<other>& p) { ptr = static_cast<Type*>(p.ptr); } void assign(const policy& p) { ptr = p.ptr; } void assign(Type* p) { ptr = p; } bool equal(const policy& p) const { return ptr == p.ptr; } bool less(const policy& p) const { return ptr < p.ptr; } public: bool operator!() const { return !ptr; } Type* operator()() const { return ptr; } template<class I> Type& operator[](const I& i) const { ASSERT(i==0); return operator*(); } Type* operator->() const { if(!ptr) throw_excpptr<excp<Type> >(); return ptr; } Type& operator*() const { if(!ptr) throw_excpptr<excp<Type> >(); return *ptr; } operator Type*() const { return ptr; } Type* New() { ptr = new Type; return ptr; } typedef policy pointer_plc; };
注意 get_counters
的实现方式:它不查找任何 Type
到 counters
的关联:只是创建新的。
总的来说,这是一个不安全的操作:将一个普通的指针传递给以这种方式定义的智能指针,应该只在其对象的生命周期中进行一次。
还要注意,从不同类型赋值是通过 static_cast
操作的,并注意 on_delete
中的简单 delete
。
这些当然是最低限度的功能。我们总能做得更好,但“如何”更好取决于不同的情况。
RTTI 对象
RTTI 对象是指至少有一个 virtual
函数(或继承了某些函数)的类。当编译器启用 RTTI 支持时,dynamic_cast
可以应用于 RTTI 对象之间的转换。但还有更多:无论您拥有什么 RTTI 类的指针,如果将其 dynamic_cast
到 void*
,它将得到最外部运行时对象的地址。也就是说:给定一个在内存中分配的“菱形”对象,无论您拥有什么指向该菱形某个基类的指针,dynamic_cast<void*>(pWhateverBase)
总是会返回相同的值;该值可用作对象的唯一标识符。
我们可以做的第一件事是,用 pointers::dynpolicy
覆盖 pointers::policy
:
template<class Type> class dynpolicy: public policy<Type> { protected: template<class other> void assign(const policy<other>& p) { assign(dynamic_cast<Type*>(p())); } void assign(const dynpolicy& p) { assign(p()); } void assign(Type* p) { policy<Type>::assign(p); } };
它只是使用 dynamic_cast
而不是 static_cast
。但是,利用前面描述的属性,我们还可以用 mappedpolicy
覆盖 dynpolicy
:
template<class Type> class mappedpolicy: public dynpolicy<Type> { private: typedef typename std::map<void*, refcounts::counters*> map_t; typedef typename dynpolicy<Type> base_t; typedef typename smart<refcounts::policy<pointers::policy<map_t>, true> > map_p; static map_p map() { static map_p p(new map_t); return p; } map_p pMap; protected: // a map will exist until the program terminates // or someone will refer it mappedpolicy() { pMap = map(); } refcounts::counters* get_counters(Type* p) { refcounts::counters*& pcnt = (*pMap)[dynamic_cast<void*>(p)]; if(!pcnt) pcnt = new refcounts::counters; return pcnt; } void on_delete() { pMap->erase(dynamic_cast<void*>(operator()())); base_t::on_delete(); } };
这里使用一个静态可用的 std::map
来将标识 RTTI 对象的 void*
与其计数器关联起来。get_counters
仅在没有为给定对象映射计数器时才创建新计数器。这使得智能指针在接收任何普通指针时都是安全的。但是,如果系统中有很多对象,该映射可能会变得非常庞大。
侵入式指针
避免这种情况的另一种方法是通过“侵入式”机制:
template<class Type> class intrpolicy: public dynpolicy<Type> { protected: void on_addref() { if((*this)()) dynamic_cast<refcountable*>((*this)())->on_addref(); } void on_firstref() { if((*this)()) dynamic_cast<refcountable*>((*this)())->on_firstref(); } void on_release() { if((*this)()) dynamic_cast<refcountable*>((*this)())->on_release(); } void on_lastrelease() { if((*this)()) dynamic_cast<refcountable*>((*this)())->on_lastrelease(); } void on_delete() { if((*this)()) dynamic_cast<refcountable*>((*this)())->on_delete(); ptr = 0; } refcounts::counters* get_counters(Type* p) { return (!p)? 0: dynamic_cast<i_refcountable*>(p)->get_counters(); } };
本质上,所有回调都被委托给被引用的对象,其中期望具有同名函数。类 stdx::refcountable
将这些函数定义为 virtual
,分配其自己的计数器并保留对自身的引用,并在 on_delete
中执行 delete this
。
class refcountable { friend struct refcounts::counters; public: refcountable() { pCnt = new refcounts::counters; pCnt->increment(*this, false); } virtual ~refcountable() { if(pCnt) pCnt->decrement(*this, false); pCnt=0; } private: refcounts::counters* pCnt; protected: virtual void on_addref() {} virtual void on_firstref() {} virtual void on_release() {} virtual void on_lastrelease() { } virtual void on_delete() { delete this; } virtual refcounts::counters* get_counters() { return pCnt; } };
快捷方式
到目前为止,我们可以组合智能指针、引用计数和指针策略来提供各种智能指针,或者从头开始创建一个新策略或通过派生现有策略来创建。
例如,stdx::smart<stdx::refcounts::policy<stdx::pointers::policy<double>,true> >
是一个强 static
-转换的指向 double
的指针。
由于所有这些声明如果经常使用可能会很麻烦,对于频繁使用的组合,我在 stdx
命名空间中提供了一些快捷方式:
template<class Type> struct ptr //intrusive ptrs, dumb-to smart safe assignment { typedef smart<refcounts::policy<pointers::intrpolicy<Type>, true> > strong; typedef smart<refcounts::policy<pointers::intrpolicy<Type>, false> > weak; }; template<class Type> struct statptr //static_cast non-intrusive ptrs, usafe dumb-to smart assignments { typedef smart<refcounts::policy<pointers::policy<Type>, true> > strong; typedef smart<refcounts::policy<pointers::policy<Type>, false> > weak; }; template<class Type> //non casting, non intrusive ptr to an array. unsafe dumb-to smart assign struct refcntvect { typedef smart<refcounts::policy<pointers::vectpolicy<Type>, true> > strong; typedef smart<refcounts::policy<pointers::vectpolicy<Type>, false> > weak; }; template<class Type> //dynamic_cast non-intrusive ptrs, usafe dumb-to smart assignments struct dynptr { typedef smart<refcounts::policy<pointers::dynpolicy<Type>, true> > strong; typedef smart<refcounts::policy<pointers::dynpolicy<Type>, false> > weak; }; template<class Type> //dynamic_cast non-intrusive, safe dumb to smart assignment struct mappedptr { typedef smart<refcounts::policy<pointers::mappedpolicy<Type>, true> > strong; typedef smart<refcounts::policy<pointers::mappedpolicy<Type>, false> > weak; };
有了这些定义,上述声明可以写成 stdx::statptr<double>::strong
。
值语义
另一个有趣的策略家族是通过考虑一个引用计数包装器而不是存储指针(提供智能指针)来存储“值”。这在您想将某些删除或创建策略与 Windows HANDLE
等结合使用时很常见。
但问题不一定局限于 Windows:例如,您可以使用数组索引等来实现相同的功能。
namespace values
包含一个 policy
和一个 mapped policy
。就像指针一样。
template< class Type, Type nullval, //a singular value for Type //a "void operator()(Type&)" //functor, doing cleanup action class CleanupFn > class policy { friend class policy; private: Type val; CleanupFn fnCleanup; protected: typedef const Type& alias_type; typedef const Type& acquire_type; typedef Type value_type; policy() { val=nullval; } ... void on_delete() { fnCleanup(val); val=nullval; } refcounts::counters* get_counters(Type* p) { return new refcounts::counters; } ... public: bool operator!() const { return val == nullval; } const Type& operator()() const { return val; } operator const Type&() const { return val; } }; //mapped values: use a globally referred //map to associate counters to values. // the existence of the map is ... refcounted(!) - // without a map, of course. - template<class Type, Type nullval, class CleanupFn> class mappedpolicy: public policy<Type, nullval, CleanupFn> { ... refcounts::counters* get_counters(const Type& p) { refcounts::counters*& pcnt = (*pMap)[p]; if(!pcnt) pcnt = new refcounts::counters; return pcnt; } void on_delete() { pMap->erase(operator()()); base_t::on_delete(); } };
同样,就像指针一样,我们也有 stdx
快捷方式:
template<class Type, Type nullval, class CleanupFn> struct hnd { typedef smart<refcounts::policy< values::policy<Type,nullval,CleanupFn>, true> > strong; typedef smart<refcounts::policy< values::policy<Type,nullval,CleanupFn>, false> > weak; }; template<class Type, Type nullval, class CleanupFn> struct mappedhnd { typedef smart<refcounts::policy< values::mappedpolicy<Type,nullval,CleanupFn>, true> > strong; typedef smart<refcounts::policy< values::mappedpolicy<Type,nullval,CleanupFn>, false> > weak; };
有关智能指针或智能值的测试可以在 *c0.cpp* 中找到。
策略化和打包
基本上有两种方法可以自定义引用计数:
- 通过定义新的策略(可能覆盖现有策略)供
smart
继承:这在需要特殊销毁行为的任何地方都可能有用。 - 通过定义一个
refcoutnable
类来存储描述数据并实现回调来为该数据执行“维护”,定义一个指向该数据的智能指针,并实现一个类来包装它。
第一点可能更传统,但在某些复杂情况下,可能需要存储大量数据结构。第二点允许在不同指针之间共享这些数据结构。
打包示例:字符串
共享字符串在语义上是值对象,它们通过共享只读数据来代替复制。当需要修改数据时,会创建一个新副本并进行修改。
template<class Type, class traits=strings::traits> class string { private: typedef std::vector<Type> data_t; typedef stdx::statptr<data_t>::strong data_p; data_p pData; //lets have a shared null string (will //simplify a lot the redirections) static data_p nullstr() { static data_p p; if(!p) p.New()->resize(1); return p; } friend class string; public: string() { pData = nullstr(); } Type* get_buffer(size_t len=-1) { if(len == -1) len = pData->size(); if(pData.get_holdcount() >1) pData.New(*pData); //clone the vector pData->resize(len+1); return &(*pData)[0]; } operator const Type*() const { return &(*pData)[0]; } bool operator!() const { return !(*pData)[0]; } void clear() { pData = nullstr(); } size_t len() const { return pData->size()-1; } string(const string& s) { pData = s.pData; } string(const Type* p, size_t n=-1) { pData = nullstr(); if(n==-1) n = traits::len(p); Type* buff = get_buffer(n); traits::copy(buff, p, n); } string(const Type& c, size_t n=1) { pData = new data_t(n+1, c); (*pData)[n] = (*nullstr())[0]; } string& operator=(const string& s) { pData = s.pData; return *this; } string& operator+=(const string& s) { size_t n = len(); size_t m = s.len(); Type* buff = get_buffer(m+n); traits::copy(buff+n, s, m); return *this; } string operator+(const string& s) { return string(*this)+=s; } ... }
管理的关键是 get_buffer
函数:如果保持计数大于 1(即数据被共享),则克隆数据。
还要注意,除了 getbuffer
之外,没有任何函数允许修改数据或以非 const 模式访问它们。这使得 std::string
即使在内部是引用计数指针,也具有值类的行为。
traits
参数必须是一个 struct
,它提供 static
len
和 copy
函数以及 struct base
,后者可用于继承其他功能。默认的 strings::traits
使用线性循环为通用 Type
实现它们,并将 base
提供为一个空的 struct
。
请注意,在提供 stdx::string
的范围内,它不是一个 OOP 字符串类,而仅仅是一个数据共享器。它作为成员函数只实现了管理数据所需的功能,但您仍然必须依赖外部标准库函数来处理字符串操作。
递归模板策略化示例:复合对象
复合对象本质上是可以相互收集的对象,其中一个对象可以包含其他对象。
这与纯粹的“泛型编程”模式(如 STL)不同,在 STL 中,集合包含通用的同构类型,集合中的每个对象都不知道自己被收集了。收集指针可以实现多态性,但对象仍然不知道集合的存在。其优点是它们(或更确切地说,它们的指针)可以成为许多集合的成员,但对象本身无法了解其“下一个”或“上一个”。
在此实现中,复合对象是节点,它们拥有自己的前、后、父……的引用。但它们也需要拥有自己的特定接口。
这是通过使节点引用以“指针”的形式实现,这些指针指向实现“节点”功能的类(通过继承“节点”类):一个“类”派生自 node,而 node 包含指向该“类”的“指针”。这是一个递归模板游戏(参见 *compounds.h*)。
template<class Derived, class traits = ... > class chain_node: public traits::base { public: ... };
您可以定义您的链式节点如下:
class yourchainable: public stdx::chain_node<yourchainable> { ... };
前和后引用是根据“指向 Derived
的指针”实现的:但是“指针”可以是……任何拥有解引用运算符的对象。哪个对象,由 traits
类定义,它必须提供 comp_dumb_traits
(使用普通的“普通”指针)或 comp_smart_traits
(使用智能侵入式指针)中出现的定义。
请注意,comp_xxx
本身不是模板,但它包含所有模板成员。虽然这稍微增加了策略本身编码的复杂性,但它简化了策略的使用,因为不需要指定它必须应用于的类型。
chain_node 类
如上所示,chain_node
保存着下一个和上一个指针。当使用 comp_smart_traits
时,前向指针是“强”的,而后向指针是“弱”的:这使得链中的一个元素能够维持后续元素,并被前一个元素维持。
set_next
和 set_prev
函数的含义很明显,但有一个不太明显的后果:“A->set_next(B)
”和“B->set_prev(A)
”并不相同:在前一种情况下,B
离开了其原始链,并插入到 A
和其原始下一个之间;在后一种情况下,是 A
加入了 B
的链,插入到 B
的前一个和 B
之间。在这两种情况下,离开原始链的是作为参数传递的节点。
hierarchy_node 类
类似地,层次结构节点是通过一个“兄弟链”实现的,带有一个父级弱指针以及“第一个”和“最后一个”子节点的强指针。当然,如果使用了 comp_dumb_traits
,则使用普通指针。
与链节点一样,层次结构节点不仅具有 set_prev
和 set_next
,还具有 set_first
、set_last
和 set_at
。为了遍历层次结构,提供了 get_xxx
函数,用于浅层(遍历兄弟节点)和深层(遍历子节点)迭代。
迭代复合对象
类 stdx::ierator<class Compound, class iter_trs = iter_traits_chain, class traits = Compound::traits_t>
具有指针语义,以及所有的增量/减量运算符。这些运算符的实现定义在 iter_trs
提供的函数中,这些函数由 iter_traits_chain
和 iter_traits_deep
提供。内部迭代器指针类型是从 traits
获取的,即 traits::types<NODE>::wptr
。
使用示例可以在 *C2.cpp* 中找到。
为了尽可能符合 STL 标准,stdx::iterator
定义了 STL 迭代器的 typedef
,而 xx_node
类具有 begin
/end
函数。但是,由于复合对象是自包含的,end
总是返回一个“空迭代器”。这无疑与 STL 的一个区别是:在 STL 中,递减一个已到达集合末尾的迭代器是合法的,但在这里是非法的。
不管怎样,std::for_each
可以工作,并且——在您自己定义的循环中——您可以将 stdx::iterator
与“end”迭代器进行比较,或者简单地使用 operator!
来检查它是否为空。
图
使用相同的策略机制,可以通过派生 graph_node
和 graph_arc
类来创建图。当使用“智能”指针时,节点维持弧(弧列表使用强指针),而弧只观察节点(通过弱指针)。因此,虽然节点必须独立存在(可能在其他地方被收集),但当没有节点引用它们时,弧将被删除,除非它们在其他地方被引用。
在此实现中,graph_node
和 graph_arc
都是递归模板,它们以派生自自身的类和派生其对应物的类作为参数。
请注意,这两个类仅包含构成图的函数(通过弧连接节点)。算法不能在这里实现,因为它们取决于节点和弧代表的内容。这主要取决于派生类内部的节点和弧数据。从这个意义上说,它们就像 stdx::string
:泛型编程类,而不是 OOP 对象。
使用示例在 *C3.cpp* 中。
迭代图
由于 graph::node
的出边和入边指针存储在 std::list
中,因此应该可以通过暴露它们的 begin/end
函数和它们的迭代器类型来迭代这些列表。
但这不切实际,因为这样的“迭代器”遍历的是指针,因此,访问弧需要双重解引用(即:operator->
无效,并且弧成员应被访问为 (**iter).member
)。为了避免这种烦琐的语法,助手 stdx::derefiterator
继承任何 STL 兼容的迭代器,并替换 *
和 ->
运算符来隐藏双重间接引用。这被用作 graph::node::arc_iterator
,它也由 begin_arcfrom
、begin_arcto
、end_arcfrom
、end_arcto
返回。
通过图路由消息
与 std::for_each
类似,迭代节点的弧或其连接节点可以通过 graph::route_arcs
和 graph::route_nodes
来完成。它们都保持一个通用的 Data&
和一个起始 Node&
,并分别应用 Fn(Arc*, Data&)
和 Fn(Node*, Data&)
。Fn
可以是任何“函数式对象”,包括 std::mem_func
返回的对象,允许将 Data&
传递给成员函数。
此机制还可以用作一种静态类型事件路由网络(因为节点可以重新路由消息)。
结论
如果您有耐心读到这里,恭喜!这篇文章可能有点长。
这当然不是工业级代码,但在您不必依赖大型库来做小事的各种情况下,它可能很有用。正如我在开始时所说,如果您已经依赖这些库,请使用它们。如果您不依赖,或者只是想尝试一种不同的方法,请将此视为一个“起点”。