让我们的一切从代码开始吧,简单起见,代码省略std名字空间:
class MyObject {
object_id obj_id_;
class_id class_id_;
map properties_;
static const type_dict& getDict();
...
};
这是个从实际的项目中整理出来的代码,简单的说明一下:properties存放了Object的属性,MyObject底层依赖一个类型系统的支撑,这个类型系统的模型就是type_dict。class_id_是记录一个对象实例所属的类型,obj_id_则是对象实例的ID.
这个对象的所有属性都是可以从外部访问的,但是,我却不能把properties_公开,因为这样一来的话,外部就可以随便指定一个property_id插入map,而这个property_id也许并没有被当前class_id_所指类型包含,或者,根本就是非法值,这将导致MyObject内部不一致。
于是,除了完成operator[]和clear这样的实现外,我还要这么干:
public:
const_iterator begin() const{ return properties_.begin();}
iterator begin() { return properties_.begin();}
...
他们的实现都很简单,只不过是一行:调用properties的相应方法就是了,可我不得不重新写一遍,这是个copy&paste工作,总是让我想打瞌睡,结果,我总是出错,因为我总是忘记做一些必要的修改。如果有类似这样一种语法:
public:
proxy const_iterator properties_.begin() const;
或者:
proxy properties_.end;
我想我会愉快的多。除了少些一些代码外,关键是代码变得清晰了,程序员通常都是懒惰的,要命的我既懒惰又健忘,保持代码清晰对我而言很重要。于是,代码变成了这样:
class MyObject : private map {
typedef map ContType;
object_id obj_id_;
class_id class_id_;
//map properties_;
static const type_dict& getDict();
...
};
然后,我可以这么写:
public:
const_iterator begin() const{ return ContType::begin();}
iterator begin() { return ContType::begin();}
这样看上去语义至少清楚了一些,可是仍然不能让我满意,那个return 语句我还是可能弄错:我把上面两句c&v,然后把begin改成end,结果改漏了一个:(
最终,using达到了我的目的,我决定这么写:
public:
using ContType::begin;
using ContType::end;
using ContType::insert;
这么写不会导致访问权的问题,看,这和我最初设想的proxy properties_.end;多么相似。这样写的好处不仅仅是简单,最重要的是表达出来的语义:转调容器的相同方法。我想任何程序员首先都会相信STL的方法,而不是我写的,即使我的代码只有一行,也可能出错。using 方法如果出错的话,编译时就不会通过。
再想想,using 只是引入一个名字,那么using对typedef的别名应该也有效。来让我们试试这个:
using ContType::iterator;
很好,它可以工作,而且还不必像typedef那样在ContType::iterator前面加一个typename.于是,我决定把map内的类型申明用using的方法引入到MyObject中来,有十几行代码。还好,和using一个成员函数一样,using后面拼写错误的话,编译器立刻就会报错。
我似乎把这个问题解决的很好了,只是还有一点小麻烦:我代理了大部分map的方法,引入map的typedef。其实只有map的swap、operator[]等等少数几个方法需要修改,可是我却要为其他部分写上几十行代码。回到我的目的上来:我只是要防止某些外部对MyObject的修改,避免导致内部不一致而已。结果,我不得不把一大堆typedef和const方法也using了个遍。增加了代码长度倒是其次,它扰乱了我的视线。直观的,别人会以为我是因为需要这些方法,所以代理了他们,但真实的涵义却是:我不关心他们(像count这种东西,我真的不知道是不是需要暴露出来),所以还是让他们保持原样。这种直观的隐喻和真实意图之间的矛盾,让我担心总有一天他会骗倒某个人。还有更好的办法吗?
再让我们看这样一段代码:
struct A{ void fun(){} };
class B : public A { using A::fun;};
试试这一句:B().fun();
编译出错!报告不能访问私有成员。这意味着我们我们能够改变一些基类成员的访问权!实际上,这种访问限制既可以放宽,也可以加强,这太好了!于是我的MyObject可以变成这样:
class MyObject : public map {
typedef map ContType;
using ContType::operator[];//这里,禁止访问map的clear和operator[]
using ContType::clear;
public:
void clear();
...
};
虽然我在public中也定义了void clear(),但这和悄悄的覆盖行为不同,using ContType::clear; 强烈的表达出关闭基类clear的语义。
这种方法我只需要写几行代码,而且更容易让我们的视线集中,还可以偷更多的懒。
这里有个小小的不足,如果我用MyObject().map::clear()这样的方法,就可以合法的调用map::clear了。不过这基本上可以认为是hack手法,不必过分去关心的。
using在这里很好的解决了我的问题,然而,他还是不能取代proxy。using无法让我有选择的代理函数的特定重载版本,例如我想公开const_iterator begin() const;而不公开iterator begin()就不能通过using实现。真希望C++会在语言层面支持proxy、delegate、reflection这样的特性,但是这种期望是在是不令人乐观。