“封装”的进阶思考--片段

 

〖轻松一刻〗:BMW和一块帆布

天要下雨了,车场上一辆BMW,工作人员拿一块帆布将它盖得严严实实。丁小明和南老师经过时看到了这一幕。

丁小明:“看,那块帆布就是一种封装,它保护了底下的BMW不受风吹雨打。”

南郁:“不,真正的封装,是BMW自身,这辆车才是完美的封装艺术”。

 

“封装”有这么多内容吗?有些教材,不就是把“publicprotectedprivate”一提,就结束了有关“封装”的内容吗?让我们回顾一下本章的历程。

 

Ø  不变式是类的灵魂

要懂得“封装”,语法与语义上第一件要理解的是“this”指针,知道它是被编译器偷会为成员函数的偷偷加一个入参,就是它。然后要能理解就算是一个看起来一个空空的类,它也会有一些“默认的行为”。

但是,关于封装的第一件有“灵魂”的事,是“类的不变式”。让我们来复习一下这些没有灵魂的封装:

Code:
  1. int x, y;  

 

说这就是一个坐标?这是裸奔。封装都谈不上,哪来的灵魂。

Code:
  1. struct Point  
  2. {  
  3.         int x, y;  
  4. };  

代码阅读者的心理负担因为这个结构的存在,而减轻了很多,但它也就如此了,  不存在更多信息的封装。

Code:
  1. class Point  
  2. {  
  3. public:  
  4.   
  5.         int GetX() const { return _x; }  
  6.   
  7.         int GetY() const { return _y; }  
  8.   
  9.    
  10.   
  11.         void SetX(int x) { _x = x; }  
  12.   
  13.         void SetY(int y) { _y = y; }  
  14.   
  15. private:  
  16.   
  17.         int _x, _y;  
  18. };  

 

这是冗余保护,struct穿上了小马甲摇身变成class,可是我们还是认得出它。

面向对象技术本来就是用来对付足够复杂的问题的。如果面对的是一个简单的程序中的某个简单的事物(此处的坐标点),那么struct版本的Point就是最佳选择。如果面对的是一个复杂的程序中的一个简单的对象,冗余保护的那个版本值得借鉴。

一个类型,可能有十数个成员数据,但如果这些成员数据之间彼此没有什么关系,那么很可能它就只适合做一个结构;或者,它只有一个内部数据,但它在和外部打交道时,需要在多种状态之间按照某种规律切换,那么这个类也需要我们好好去封装,以维护它的 “不变式”。

Ø  需要拷贝构造和赋值吗?

当一个类在构造对象时,需要分配额外资源,通常就需要一个配套的析构函数,用以在对象消逝前释放资源。当然,并不是说资源只能在析构时释放,但通常会是个好时机可以确保资源被安全回收。

如果仅仅是这样,那倒也简单,一对一地写构造和析构函数就是了;但是想到对象还会以另一个对象为模子创建出来,这就需要一个拷贝构造函数了。再想到对象还可以彼此间赋值,于是要重载赋值操作符,这下子要放下自己原来已有的,然后再接受对方已经有的……这些过程,都要考虑“深拷贝”或“浅拷贝”问题。

是时候做一个提示了:并不是所有类(的对象)都需要“拷贝”或“赋值”的。比如写一个窗口(Windows)程序,会有许多和窗口类,这些类通过就在需要时构造出一个实际窗口,很少需要进行“拷贝”,或者让A窗口赋值给B窗口。再比如,我们自己定制的某些容器类(比如前述的IntegerList),在实际应用中,有时候没有复制整条链表的需求。

当某个类不需要 “拷贝构造”或“相互赋值”的能力时,可以直接明了的将这两个函数声明为私有的:

Code:
  1. class FontDialog  
  2.   
  3. {  
  4.   
  5. public:  
  6.   
  7.         FontDialog(std::string const& title)  
  8.   
  9.                : _title(title)  
  10.   
  11.         {}  
  12.   
  13.    
  14.   
  15.         ~FontDialog() {}  
  16.   
  17. private:  
  18.   
  19.         FontDialog(FontDialog const& o);  
  20.   
  21.         FontDialog& operator = (FontDialog const& o);  
  22.   
  23.    
  24.   
  25.         std::string _title;     
  26.   
  27. };  

 

注意:我们把FontDialog的拷贝构造和赋值操作符声明为私有的,这就代表了外部再也不能调用这两个函数,既然外部不会调用这两个函数,自然我们也就不需去真正实现它们。

下面的代码通过不过编译:

Code:
  1. FontDialog dlgA("Select font ...");  
  2. FontDialog dlgB(dlgA); //编译不过,因为调用私有的成员函数  
  3.   
  4. FontDialog dlgC("");  
  5. dlgC = dlgA; //也编译不过,原因同上  

 

〖小提示〗:STL 容器里的元素,必须“可复制”

STL的容器,默认采用“值”存储。并且由于要支持添加,排序等,所以元素之间 经常需要复制,所以想要放入STL标准容器的对象,都必须能拷贝及赋值。不过,如果一个对象在实际逻辑上确实不能复制,这时如果又需要放在容器里,最正确的做法就是只在容器里保存它们的指针(相应的,通过需要将元素在堆里分配)。

 -------------------------------------------------------------------------------

 如果您想与我交流,请点击如下链接成为我的好友:
http://student.csdn.net/invite.php?u=112600&c=f635b3cf130f350c

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 12
    评论
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

南郁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值