对象属性的C++实现
高级语言的面向对象编程模型一般都支持“属性”接口,例如 Visual Basic 和 C#。在C#中,通过访问属性,不仅可以存取数据,还可以在存取时执行其它操作,在代码形式上,改变属性值可以写成给变量赋值的形式,例如:Form1.Size = System.Drawing.Size(640,480),而不是调用函数:Form1.SetSize ...(实际上Form没有SetSize函数)。C++语言不支持对象属性,对象的可执行接口只有函数,所以C++里没有“属性”和“方法”的概念。但是通过一些编程的技巧,可以实现一个属性访问器,使 C++ 代码可以具有和 C# 相似的编程风格。
属性访问器模板
代码如下:
template<typename TObject, typename TProperty>
struct PropAccessor{ TObject* Instance; PropAccessor(TObject* instance): Instance(instance){} operator TProperty(); TProperty& operator = (TProperty value);};
其中:operator TProperty() 用来实现属性的"get"部分,TProperty& operator=(TProperty value) 用来实现属性的"set"部分。
这个模板是不能直接使用的,两个运算符函数都没有实现,为了可以使用,需要继承模板并实现这两个运算符。这样就定义了一个模拟的“属性”。
属性访问器的实现
下面是一个具体的实现例子。MeshNode是用于3D场景的模型对象,float3是一个向量类型,表示Mesh在场景中的位置,float4x4是一个矩阵类型,表示Mesh的变换矩阵。演示的代码是MeshNode中的Position属性。
union float3 //一个简单的向量类型。{ float m[3]; struct { float x, y, z; }; float3() { for(int i = 0; i < 3; i++) { this->m[i] = 0.0f; } };};union float4x4 //一个简单的矩阵类型。{ float m[16]; struct { float _11, _12, _13, _14; float _21, _22, _23, _24; float _31, _32, _33, _34; float _41, _42, _43, _44; }; float4x4() { for(int i = 0; i < 16; i++) { this->m[i] = 0.0f; } };}; class MeshNode{public: //属性访问器的实现类。这个类直接嵌套在MeshNode,用来实现属性"Position". class PROP_Position: public PropAccessor<MeshNode, float3> { friend class MeshNode;//这个声明使MeshNode可以创建PROP_Position对象,而外部调用者不能创建PROP_Position,因为下面的构造函数是私有。 private: PROP_Position(MeshNode* instance): PropAccessor(instance){}; public: //Property "get":实现了模版中对应的运算符,TProperty被替换为float3。这里只是简单的取得数据,也可以做任何其它操作。 operator float3() { float3 rv; rv.x = this->Instance->m_WorldMatrix._41; rv.y = this->Instance->m_WorldMatrix._42; rv.z = this->Instance->m_WorldMatrix._43; return rv; }; //Property "set":实现了模板中对应的运算符,TProperty被替换为float3。这里只是简单的保存数据,也可以做任何其它操作。 float3& operator=(const float3& value) { this->Instance->m_WorldMatrix._41 = value.x; this->Instance->m_WorldMatrix._42 = value.y; this->Instance->m_WorldMatrix._43 = value.z; return value; }; };//private: // //... float4x4 m_WorldMatrix; //public: // //... // //属性"Position":取得/设置对象的位置。(前面"PROP_Position"的一堆代码就为了实现这个). PROP_Position Position() { return PROP_Position(this); };};属性访问器的使用
有了这些代码,就可以使用"Position"属性了,MeshNode中的Position函数返回的是一个PROP_Position对象,通过这个对象的运算符使代码具有使用属性的风格。例如:
MeshNode* my_mesh = new MeshNode();float3 my_pos;my_pos.x = 10.0f;my_pos.y = 10.0f;my_pos.z = 10.0f;my_mesh->Position() = my_pos; //有了使用属性的代码形式。这时调用了"MeshNode::PROP_Position::operator=()"函数。float3 world_pos = my_mesh->Position(); //和普通C++函数形式相同,但是用到了属性访问器,这时调用了"MeshNode::PROP_Position::operator float3()"函数。
简单总结
这个属性访问器的优点就不用多说了,主要讨论缺点。具体开发中,可以根据具体情况来决定是否使用它。
1. 增加了代码量。需要编写多余的代码,为每个属性接口实现一个访问器。
2. 和传统的"getPosition"和"setPosition"函数相比,通过属性访问器对数据访问,效率略有下降。在"get"过程中,额外的操作是:创建并返回一个访问器对象,调用运算符"operator TProperty()";在"set"过程中,额外的操作是:创建并返回一个访问器对象,调用运算符"TProperty& operator = (TProperty)",且属性值多了一次引用传递。
3.不能被重载。这样实现的属性不能被重载,如果需要重载,需要更复杂的实现方法。如果不想折腾的话,在程序的设计中就要尽量避免重载属性。
总体来说对性能影响不大,在不苛刻要求效率的情况下,这些额外的开销可以忽略。只是增加了实现属性访问器的开发工作,如果希望属性可以被重载,需要更多的技巧和工作量,实现方法其实也没有难度,大家可以自由发挥。