Protean Charm 或 C++ 中的绑定对象






4.85/5 (15投票s)
本文描述了一种 C++ 对象的智能绑定方法。
引言
具有对象绑定机制的应用程序众所周知、非常受欢迎,而且也得到了广泛应用。电子表格就是一个例子。电子表格中与特定行和列绑定的变量可能依赖于同一文档中的一个或多个其他变量。请注意,在本文中,我对给定应用程序内的对象绑定不感兴趣。(显式或隐式委托的方法众所周知)。我想解决的问题是:是否可以在类级别实现对象绑定,并创建一个“隐藏”机制来交换这些类的某些属性的值?在代码层面,这意味着:给定图 1 中指定的三个类,是否有办法编写图 2 中的程序,以产生图 3 中显示的九行内容?
class Object1
{
int A_;
string B_;
long C_;
REFLECTED_OBJECT(A_);
REFLECTED_OBJECT(B_);
REFLECTED_OBJECT(C_);
}
class Object2
{
int A_;
string B_;
REFLECTED_OBJECT(A_);
REFLECTED_OBJECT(B_);
}
class Object3
{
int A_;
long C_;
REFLECTED_OBJECT(A_);
REFLECTED_OBJECT(C_);
}
main()
{
Object1 object1;
Object2 object2;
Object3 object3;
object1.A = 12;
object1.B = "Welcome to C++";
object1.C = 444;
object1.Print();
object2.Print();
object3.Print();
object2.B = "Welcome to reflection!";
object1.Print();
object2.Print();
object3.Print();
object3.B = 123456;
object3.A = 77;
object1.Print();
object2.Print();
object3.Print();
}
object1={12,"Welcome to C++",444}
object2={12,"Welcome to C++"}
object3={12,444}
object1={12,"Welcome to reflection",444}
object2={12,"Welcome to reflection"}
object3={12,444}
object1={77,"Welcome to reflection",123456}
object2={77,"Welcome to reflection"}
object3={77,123456}
简要思考表明,该问题可以在以下假设下解决
- 对象是类的样本;对象属性是这些类的成员。
- 两个类被认为相互关联,如果这些类的某些(或所有)属性相同(例如名称和类型),并且其中一个对象的属性值发生变化会自动导致另一个对象中相同属性的值发生变化。
- 如果系统中出现新对象,其属性会自动设置为其他对象中相似属性的当前值。
每个类必须有一个链接属性的容器,而每个属性必须保留以下值
- 属性类型
- 属性名称
- 指向类中属性值的指针
链接的类必须有一个指向全局对象的指针,该对象管理值交换系统。管理器包含指向链接类的指针向量。如果一个类的属性值发生变化,管理器应该找到具有相同属性的对象(类的实例),然后更改这些属性的值。
交换机制通过赋值运算符运行。因此,属性也是一个对象,并且应该包含指向其所属类的指针。系统架构如图 4 所示。
实现
每个属性被链接的类都必须继承接口 IReflection
。此接口包含一个链接属性的向量 <object_ptr>objects_ptr
,以及用于获取/设置属性值和调用复制过程的方法。抽象类 IReflection
包含一个指向 ExchangeManager
对象的指针,该对象包含有关所有支持 IReflection
接口的类的信息。当我们创建这样的类时,后者会自动将自身添加到管理器容器中;当我们删除这样的类时,它会将其自身从容器中移除。请注意 IReflection
类的构造函数和析构函数。
属性的参数由 object_ptr
类描述。此类包含类成员的名称和类型,以及指向它的指针。参数列表由宏 REFLECTION_START
、REFLECTED_OBJECT
和 REFLECTION_FINAL
描述,并在类在 Init
过程中初始化时形成。
ExchangeManager
类是一个单例。它包含一个指向 IReflection
类型类的指针容器,并包含 Initiation
和 Distribution
方法。第一个方法允许类属性获取初始值。第二个方法允许将更改的值发送给其他对象。
之前提到,属性也是一个对象(标量类型)。其属性在 Type
类中描述。该类包含属性的值、多个构造函数和赋值运算符。目前,以下四种变量类型——Int
、Long
、Bool
和 String
——在下面描述。
typedef Type<int> Int;
typedef Type<long> Long;
typedef Type<bool> Bool;
typedef Type<string> String;
如有必要,可以扩展此列表。为此,我们需要在文件 TypesWrapper.h 和 Reflection.cpp 中添加两行。例如,如果有人更喜欢使用实数,则需要在第一个文件中添加 typedef Type<double>Double
,在第二个文件中添加 TransformBase<Double> transformDouble
。
系统辅以几个支持接口和类。它们提供了一种相对简单的方法来分配和更新各种类型的属性,并将属性值转换为文本。TransformBase
和 ConversionHelper
类分别派生自接口 ITransform
和 IConversion
。TransformHelper
类是一个单例对象类型,并包含一个用于转换数据类型的容器。此类在 IReflection
类的方法 Update
和 Exchange
中使用。ConversationHelper
类在函数 fooConversion
中使用,该函数又在 History
类中使用。此类的一个对象位于主程序中,并监视变量值的任何变化。模板 Destroyer<SINGLETON>
和三个附加类——Destroyer<ExchangeManager>
、Destroyer<TransformHelper>
和 Destroyer<ConversationHelper>
——用于在应用程序退出时正确地销毁对象 ExchangeManager*
、TransformHelper*
和 ConversationHelper*
。
结论
文章附带的项目包含了创建和成功运行引言中描述的程序所需的所有必要组件。类描述可以在文件 CheckReflection.h 中找到。此描述与前面提供的描述几乎相同。测试程序的文本在文件 CheckReflection.cpp 中。
#include "CheckReflection.h"
void main()
{
History history
;
Object1 object1;
Object2 object2;
Object3 object3;
object1.Number () = "12";
object1.Message () = "Welcome to C++";
object1.Value () = 444;
object1.Print();
object2.Print();
object3.Print();
object2.Message () = "Welcome to reflection!";
object1.Print();
object2.Print();
object3.Print();
object3.Value () = "123456";
object3.Number () = 77;
object1.Print();
object2.Print();
object3.Print();
history.Print();
}
只需运行程序,即可确保结果与引言中描述的一致。
1>Object1={12,"Welcome to C++",444}
1>Object2={12,"Welcome to C++"}
1>Object3={12,444}
1>Object1={12,"Welcome to reflection!",444}
1>Object2={12,"Welcome to reflection!"}
1>Object3={12,444}
1>Object1={77,"Welcome to reflection!",123456}
1>Object2={77,"Welcome to reflection!"}
1>Object3={77,123456}
历史
- 2009 年 2 月:版本 1.0。
- 环境:Visual Studio .NET 2008。