转:Property in C++
2011年03月14日
看了《More Effective C++》、《Modern C++ Design》等书,总觉得应该上手练习一下……于是我想到了“属性”。我希望我所完成的属性具有以下特性:
1. 语法上与C#、ActionScript保持一致,即:Obj.Property = propValue和PropValueVar = Obj.Property;
2. 根据被使用的context(l-value与r-value)使用预先设定的getter和setter函数;
3. 既然属性中只是调用了getter和setter函数,那么应该不占用内存;
4. 声明属性应尽量简单,类似这样的语法:property int propValue(get = &Class::Getter, set = &Class::Setter);
5. 有readonly属性、static属性、static readonly属性等……
看来我应该使用ProxyClass(以完成某项功能为唯一目标的“代理”类,见《More Effective C++》条款30)。这里,使用ProxyClass来区分l-value与r-value是再标准不过的用例。
Level 1
第一份实现:
template
class Property
{
private:
Property() {}
~Property() {}
public:
operator PropType() //used as r-value
{
//call host class’s getter function
return getter();
}
Property& operator=(PropType rhs) //used as l-value
{
//call host class’s setter function
setter(rhs);
return *this;
}
};
operator PropType()可以自动将Property隐式转换成PropType,即用为r-value的情况;operator=()在Property被赋值时调用,即用为l-value的情况。有了这2个运算符重载,已经可以实现目标1,对于目标2也给出了足够的空间。
Level 2
但问题马上就出现了:上述代码中的加粗行,只是伪代码而已;如何真正实现对getter与setter的调用?
最显然的方案就是在Property中保存HostClass(持有此Property声明的类)对象的指针与getter和setter 函数的指针(成员函数指针),然后调用。但就算是这样显然的实现,也有个问题:怎么构造Property。现在Property必须在构造函数中传入这3 个指针,这就导致使用Property的类的构造函数中必须加入构造Property的相关代码(此问题可以使用std::mem_fun_t来解决,其实这正是我的第一个实作版本)……除去这些不算,现在Property类开始占用内存了。想象一下当一个类中有大量的Property,每个 Property保存有关于HostObject的3个指针……
我们只是需要简单的将属性重定向到一个getter函数与一个setter函数而已。为了充分压榨Compiler的劳动力,想想在目前的情况下哪些东西可以在编译期确定。2个成员函数指针显然可以。但我们仍旧需要HostObject的指针。好吧,我承认当初我自己也没有搞定这个问题,直到我读了《Imperfect C++》第35章(本文的参考)。我们有Property类的this指针,也知道Property对象作为HostObject对象的一部分。它们各自的this指针之间的偏移量正是那第三个“编译期常数”。位于stddef.h中的offsetof宏正可以帮助我们计算类与成员间的偏移量。这样,通过 Property对象内部的this指针加3个编译期常数,可以推导出 HostClass*、&HostClass::getter、&HostClass::setter。
Level 3
下一步就是将这3个常数作为模板常数参数来生成我们的Property类。新的问题再次产生,Property模板参数必须要在 HostClass声明中声明;而偏移量此时并不能计算,因为HostClass还未完成声明……无论如何,你都会得到一个HostClass undefined的编译错误。《Imperfect C++》再次帮助了我们:将计算偏移量放入一个静态成员函数中,并将该函数作为模板参数代替偏移量本身,只要你在声明Property前声明了这个静态成员函数就可以做到。
看看我们目前为止完成了哪些工作:
template
class Property
{
private:
friend PropHost;
Property() {};
public:
Property& operator=(PropType rhs)
{
// offset this pointer to host class pointer
// and call member function
((PropHost*)((unsigned char*)this - (*PropOffset)())
->*SetterFn)(rhs);
return *this;
}
operator PropType() const
{
return ((PropHost*)((unsigned char*)this - (*PropOffset)())
->*GetterFn)();
}
};
很不错吧?能够完成功能,并且不占用内存。更重要的是,我们可以指望这些代码都被内联进使用属性的地方,从而将额外开销降低为0。将 HostClass声明为友元可以防止Property在类外被构造(如果你硬要在类的成员函数中构造也没办法……)。相比第一份实现,我去掉了 private的析构函数声明,理由是既然Property不能被构造,那么也没必要刻意去防止被析构。Property被析构的唯一情况是delete &TestObj.TestProperty,不过同样的语法也可以被应用在内建型别上(即:delete &TestObj.intMember),因此没有必要将阻止写出这种垃圾代码的责任揽在自己头上。
Level 4
让我们来看看怎么使用这个类来声明属性:
class TestClass
{
private:
int getIntValue() const
{
return m_Value;
}
void setIntValue(int v)
{
m_Value = v;
}
int m_Value;
static int Offset()
{
return offsetof(TestClass, intValue);
}
public:
Property intValue;
};
TestClass t;
t.intValue = 5;
cout PropName
参数Modifier为访问修饰符,其他的意义都很明了。有了这个宏,在类中声明属性就变得很简单:
DECLARE_PROPERTY(TestClass, public, int, intValue, getIntValue, setIntValue);
如你所见,这样的声明还是显得冗长。我们希望可以不用写无数遍HostClass名字,不用加一个别扭的public,最好是这样:
public:
DECLARE_PROPERTY(int, intValue, getIntValue, setIntValue);
能做到吗?能,但是还有一个问题需要说明。
Level 5
C++中不允许长度为0的struct/union/class真正存在。不信的话可以测试如下代码:
struct empty {};
cout 内存奖励又可以不用写额外的成员访问。更妙的是,通过在这个匿名 union中声明一个“private”的typedef TestClass this_class,可以做到在属性声明中不重复写HostClass……
可惜的是,这样的结构并不可行:关键就在于匿名union结构不允许有protected和private成员。关于typedef的美梦破灭了,另一个本来做得好好的美梦也破灭了:在我们的DECLARE_PROPERTY中有私有的静态成员函数用来计算属性的偏移值,现在除非把它改成 public,否则别想过Compiler这一关。《Imperfect C++》中的做法是将计算偏移量的函数声明放在另外一个宏中,这样需要用户自己来使用这个宏――感觉离目标4愈发遥远了。
让我们稍许妥协一下。你觉得t.Properties.intValue相比t.intValue来说怎么样?如果还能忍受就往下看吧。
我们使用具名union来代替匿名union。好处有很多,最重要的是它可以拥有private成员。因此之前关于typedef的美梦也可以继续了。而且,使用union来组织属性,这些属性在类中的偏移就都一样了,可以只写出一份Offset函数。另外,使用 t.Properties.intValue这样的属性访问语法,可以提醒用户这只是个属性而不是真正的数据成员,以减少他们将属性用作函数的 reference参数与pointer参数。
#define BEGIN_PROPERTIES_WITHNAME(HostClass, Name) \
union __Properties \
{ \
private: \
typedef HostClass __HostClass; \
static int __Offset() { \
return offsetof(__HostClass, Name); } \
public:
#define END_PROPERTIES_WITHNAME(Name) } Name
#define BEGIN_PROPERTIES(HostClass) \
BEGIN_PROPERTIES_WITHNAME(HostClass, Properties)
#define END_PROPERTIES() \
END_PROPERTIES_WITHNAME(Properties)
#define PROPERTY_READWRITE(PropType, PropName, Getter, Setter) \
struct { Property PropName; };
由于持有属性的HostClass从PropHost变为了PropHost::__Properties,因此Property中的友元声明也需要作相应修改。
请注意我在PROPERTY_READWRITE中使用了匿名struct结构来包覆Property。原因是C++对于union的另一个限制:union的成员不能有默认的构造函数或其他不常用的构造函数。大致的理由我猜是因为union根本不知道要构造哪个成员而不能保证成员的默认构造函数被正确调用吧。加上这条可以绕过这个限制;我觉得这大概可以算是个bug(在Visual Studio 2008中),不过好在我们的Property不需要构造函数。
现在,在类中声明属性变得很直观:
class TestClassA
{
public:
explicit TestClassA(int v) : m_Value(v) {}
private:
int getIntValue() const
{
std::cout 的基础上做出typedef Property ReadonlyProperty。遗憾的是,C++再次阻止了我这一企图,原因在于偏特化的常数参数类型不能有依赖性。我们的Setter的类型 void (PropHost::*SetterFn)(PropType)刚好依赖于PropHost与PropType……于是只能重写一个新的 ReadonlyProperty,与Property相比去掉了operator=以实现只读访问;
2. C++的默认operator=机制允许ReadonlyProperty如此使用:
t.Properties.readonlyValue = t.Properties.readonlyValue;
我们当然不能允许此种情况发生。因此,可以显式在ReadonlyProperty声明中进行禁止:
private:
ReadonlyProperty& operator=(const ReadonlyProperty&);
可以不用定义(要定义就是{}):因为从来没有被使用过(已经被禁止掉了)。问题是将ReadonlyProperty放在union中。C++的 union不但不允许默认构造函数,连此类的operator=同样也不行。不过我们通过匿名struct已经绕过这个问题。
Level 7
还有2个问题。
属性的类型如果不是内建型别,而是struct,pointer,抑或是class怎么办?
解决方法是不要解决。没有什么可担心的,直接将reference type或pointer type传给Property模板。具体的行为不在于Property,而在于你使用的Getter和Setter函数。不过,这样使用起来有点不方便,具体来说是const修饰符作用在Getter与Setter函数的签名上。可以写相应的const版本的Property类来满足需求。
另外一点。看下面的代码:
// inside a class declaration...
private:
struct Point
{
int x;
int y;
};
public:
BEGIN_PROPERTIES(Line)
PROPERTY_READWRITE(Point&, Start, getStart, setStart)
PROPERTY_READWRITE(Point&, End, getEnd, setEnd)
END_PROPERTIES();
//continue class declaration...
那么,如何取用Point::x或Point::y就成了一个问题。我们只能这样写:
int startX = static_cast(topLine.Properties.Start).x;
无论如何这都令人厌烦。
可以在Property中添加operator*,让其返回这个属性的真正类型:毕竟operator*的意思就是取值。
int startX = (*topLine.Properties.Start).x;
另外,operator*对于内建型别也是有意义的。看如下代码:
float getAvg(float x1, float x2)
{
return (x1 + x2) * 0.5f;
}
float avg = getAvg(*t.Properties.intValue, *u.Properties.intValue);
如果没有operator*,那么这样的隐式转换(int->float)是不可能成功的。
原文址:http://xiaolingyao.spaces.live.com/blog/cns!6A8F02D95D2DDE46!201.entry
发表评论
-
Win7环境下安装Mac OS双系统及Iphone开发SDK
2012-01-20 00:22 788Win7环境下安装Mac OS双系统及Iphone开发SDK ... -
WIN32 SDK中树形视图与图像列表的配合使用实例(一)
2012-01-20 00:21 827WIN32 SDK中树形视图与图像列表的配合使用实例(一) ... -
PDF文件的读写(使用SDK).(三).PoDoFo的简单应用
2012-01-20 00:21 1698PDF文件的读写(使用SDK).(三).PoDoFo的简单应用 ... -
使用Azure SDK 1.4.1中的Web Deploy
2012-01-20 00:21 535使用Azure SDK 1.4.1中的Web Deploy ... -
宇龙酷派WINCE6.0 SDK配置方法
2012-01-20 00:21 1111宇龙酷派WINCE6.0 SDK配置 ... -
传说中的演讲稿
2012-01-19 01:17 556传说中的演讲稿 2011年04月07日 ... -
感恩演讲稿
2012-01-19 01:16 625感恩演讲稿 2010年10月21 ... -
升学宴演讲稿
2012-01-19 01:16 662升学宴演讲稿 2010年08 ... -
宿命论,三年前的演讲稿!,怀念那些力不从心的光景。。
2012-01-19 01:16 631宿命论,三年前的演讲稿 ... -
电脑硬件参数知识cpu篇
2012-01-17 00:35 759电脑硬件参数知识cpu篇 2011年05月12日 看参数 ... -
[论文]企业网中代理服务器的检测程序开发
2012-01-17 00:34 804[论文]企业网中代理服 ... -
4-25Linux下的一些简单网络配置命令介绍
2012-01-17 00:34 7104-25Linux下的一些简单网络配置命令介绍 2011年0 ... -
Leica TPS基础知识
2012-01-17 00:34 905Leica TPS基础知识 2011年0 ... -
如何培养一年级学生良好的学习习惯
2012-01-15 14:55 597如何培养一年级学生良 ... -
培养一年级学生良好学习习惯
2012-01-15 14:55 622培养一年级学生良好学 ... -
《烟灰缸里的回忆》第一章节
2012-01-15 14:55 672《烟灰缸里的回忆》第 ... -
如何培养一年级学生良好的学习习惯
2012-01-15 14:55 493如何培养一年级学生良好的学习习惯 2011年06月28日 ...
相关推荐
在C++编程中,删除特定路径下的所有文件是一项常见的任务,尤其在日志管理、临时文件清理或资源优化等场景中。以下是一个关于如何在C++中实现这一功能的详细解释。 首先,我们需要理解C++标准库提供的基本文件操作...
The goal of this guide is to manage this complexity by describing in detail the dos and don'ts of writing C++ code. These rules exist to keep the code base manageable while still allowing coders to ...
可以使用`value`关键字创建值类型,或者使用`__declspec(property)`和`[FieldOffset]`等特性来控制内存布局和封送行为。 ```cpp // 定义一个需要封送的结构体 [StructLayout(LayoutKind::Sequential)] value struct...
4. **泛型编程和模板元编程**:Call_traits、Concept_check、Enable_if、In_place_factory、Mpl、Property_map、Static_assert、Type_traits等库,帮助开发者编写更加灵活和高效的模板代码。 5. **数学和数值计算**...
:: QuickReport 4.0 for Delphi and C++Builder :: :: :: :: QUICKRPT.PAS - MAIN UNIT :: :: :: :: Copyright (c) 2007 QBS Software :: :: All Rights Reserved :: :: :: :: web: ...
"Custom Font in Property Sheets"这个话题涉及到如何在Property Sheets中自定义字体,以达到更好的视觉效果或者解决特定操作系统(如Win8)上的字体显示问题。 首先,我们需要理解`CCBPropertySheet`。这是...
Q_PROPERTY(QJsonObject myProperty READ myProperty NOTIFY myPropertyChanged) public: explicit MyCppObject(QObject *parent = nullptr) : QObject(parent) {} QJsonObject myProperty() const { return ...
2021 11.27,2021 Pure C++大会在深圳举办。今年又会给大家带来什么新的有趣的东西呢?C++20?是的,coroutine,executor,模式...宋豪杰_PURECPP - INHERITABLE C++ PROPERTY - AN IMPLEMENTATION OF C++ PROPERTY.pdf
锚点.centerIn: parent Transform { Rotation { id: rotation origin.x: parent.width / 2 origin.y: parent.height / 2 angle: 0 // 初始角度 onAngleChanged: { if (angle > 360) angle %= 360 // 限制...
Fixed : Issue with setting the focus on the form (Can be activated / deactivated with ShowActivated property) in TTMSFMXPopup Fixed : Issue with BitmapContainer assignment V1.1.0.0 New : ...
的Implementing a Property in C++ 以下是译文 本文由Emad Barsoum投稿。 开发测试环境:Visual C++ 7.0, Windows XP sp1, Windows 2000 sp3 摘要 本文试着在C++中不使用任何扩展技术模拟C#(或其他语言)中的...
在C++和Qml的交互中,有时我们需要将C++中的自定义类型数据传递到Qml界面中。这个过程涉及到Qt的元对象系统(Meta Object System)和Qml的上下文属性(Context Properties)。本文将深入讲解如何实现C++自定义类型到...
This patch addresses a number of issues in RAD Studio 10.4, pertaining to Delphi Compiler, the RAD Studio IDE in general and the new LSP-based Code Insight in particular, plus C++ Builder Android ...
IAPWS_IF97(FUN,IN1,IN2) is 104 functions of water properties and derivatives, based on the International Association on Properties of Water and Steam (http://www.iapws.org). Thermodynamic, ...
2. `void manage::in_cangku()`:入库函数,接收用户输入的货物信息,将其写入`date.dat`文件,实现库存的增加。 3. `void manage::out_cangku()`:出库函数,读取`date.dat`文件,查找并处理用户指定的出库商品,...
26.zip ActiveX Control Property Page Container ActiveX控制属性页容器(6KB)<END><br>27,27.zip a popup list box like that in Visual C++ 6.0 一个具备VC6.0风格的弹出式列表框(6KB)<END><br>28,28....
donut_demo.zip MDI and Tab WebBrowser(168KB)<END><br>33,JCB.zip A Java Class Browser written in C++ using WTL(139KB)<END><br>34,MenuBtn.zip Use of a Push button with a drop down menu(21KB)...
在描述中提到的"propsheet_in_dialog.zip_PropertySheet_dialog_sheet_visual c_控件"涉及到的是如何在对话框(DialogBox)中使用PropertySheet控件,这是一个在Windows应用程序中组织多个选项卡对话框的实用工具。...
add3dtext.zip Placing a 3D Logo Text In the PropertySheet Button Area 附加功能是控制PropertySheet区域特别是按纽部分的颜色(37KB)<END><br>10,proppage.zip Modifying Property Sheet Templates on ...
这是曾经做过的一个项目时写的一个动态库,用c++来操作Excel,包括以下功能: //===================================mainly function============================ //FuncName: xls_create //function: create ...