C++基本功和 Design Pattern系列 Leaf Class Design

所谓的Leaf Class, 就是在类层次中最低下一层,不能被其他任何的类所继承的。在Java中可以用final来表示。为了简化这一章的内容,我们不考虑template的情况,以及不考虑从其他的类继承而得到的leaf class的情况,这样就不用考虑overloading和 virtual的问题。另外,inline的使用在不在今天的文章范围内。关于template,inline和overloading,将在以后的内容中介绍。
==================== Design Principle ====================


设计一个class,有一个最主要的宗旨,那就是:容易使用,而不容易犯错误。

相信大部分的的programmer都是从独自编码开始的,这样就造成了一个问题:因为对于所有的实现细节都非常熟悉,所以在设计上也不太关心是不是有防止其他使用者犯错误的机制。因此,根据Aear的个人经验,在设计class的时候,要假设这个class的使用者不是你自己。实际上在真正的团队开发中,你永远无法估计别人如何使用你的类。你只有尽量的使用良好的设计来防止别人错误的使用你的代码。

==================== Access Control ====================

对于类的访问控制,由3个关键字来管理: public, protected, and private。下面是public, protected, and private 的作用范围:

public: 可以被任何人访问
protected: 可以被继承类,继承类的friends,以及类成员和friends访问
private: 只可以被类成员和friends访问

这里值得注意的是protected和private区别仅仅在继承类上,由于我们讨论的是Leaf Class, Leaf class 不会被其他类继承。因此,Leaf Class 不需要protected. 所以我们讨论的范围缩小到 public 和 private.

关于public其实很简单,就是谁都可以访问,相当于structure,这里就不详细说明了。关于private有必要说下。首先,private起到的是访问保护, 而不是可见行保护. 也就是说,一个private member对程序来说是可见的,但是是不可访问的。举个简单的例子:

class Test {
public:
    Test(double i);
private:
    Test(float i);
}

Test k(1.0f); // Error, Test(float i) is private

对于Test有2个constructor,其中一个public,一个private,当我们运行k(1.0f)的时候,compiler并不直接去进行类型转换把float装成double,而是尝试调用Test(float i),为什么呢? 因此compiler在编译的时候,先会进行overload resolution,根据k(1.0f)的结构,使用的function是Test(float i)。在此之后,compiler才会进行access check. 这样很容易造成让人迷惑的情况,所以:不要把相同的overloaded methods 放在不同的访问控制域中

其次, private只是在编译的过程中进行访问控制检查,并无法在程序运行中进行控制。因此,即使是private member,也能够通过对内存操作进行修改。所以在写程序的时候,尽量不要做类似的操作,这样很容易破坏数据的一致性。

下面让我们来看看什么需要放在public里,什么需要放在private里。首先要说明的是,所有的data member,也就是attribute,需要放在private中。举个例子来说明这么做的重要性:

class String {
public:
    ...
    char * pStrBuf;
private:
    int StrLength;   

}

String TempStr;

TempStr. pStrBuf = "123123";
这里我们可以访问pStrBuf并改变其内容,但是数据的一致性就破坏了。为了有效的保护private中的数据,还需要注意以下2条:


1. 不要在类的method中返回private member的非const reference
2. 不要在类的method中返回private member的非const pointer

这样就基本上能保证client无法通过正常的途径破坏class内部数据的一致性了。

==================== 防止被继承 ====================

在Java中,我们可以通过Final来使一个class无法被其他class继承,但是C++中不提供final的关键字,我们可以通过其他途径来防止类被继承。

首先,在写类的时候,使用non-virtual destructor,就是明确的告诉使用者,这个类是不应该被继承的。当然,别人也可以继承你的类,但是如果你只有public和private两种access control,继承你的类和不继承你的类没有大的区别。这就是为什么leaf class不需要protected member的原因。如果你有protected member,那么别人可以通过继承你的类,来进行一些你不希望的操作,从而破坏内部数据的一致性。

但是,我们也有另外一种类方法,来硬性的使得继承类是非法的。其代码如下:

class Test {
public:
    static Test* CreateInstance() { return new Test(); };
    ~Test() {};

private:
    Test() {};
    Test(const Test &);
    Test& operator= (const Test &);
};
我们看到Test() 是private,因此,如果我们编译下列代码,会得到错误:

    Test P; // Error, private constructor cannot be called

同样,如果我们从Test Derive一个class出来,也会发生错误。在这个类声明里,需要注意的是Destructor ~Test()是public,因为我们通过CreateInstance new 一个Test(),需要通过client来delete掉它,要不就会发生memory leakage.所以下面的代码是合法的:
   
    Test * TempObj = Test::CreateInstance();
    delete TempObj;

使用CreateInstance有很多好处,比如防止被继承,而且可以有效的控制instance的数目,在优化的时候还可以使用object pool从而提高速度,等等。在Test里,copy constructor和operator= 一般是要放在private里边的,这样可以防止很多不通过CreateInstance创建Test的途径,比如:

    Test * TempObj = Test::CreateInstance();
    Test P(*TempObj);      // Error
    Test P = *TempObj;     // Error

==================== Explicit Constructor ====================
关于Explicit Type Conversion和Implicit Type Conversion已经在前几章讲过了,对于Class的constructor来说,只要有可能引起implicit type conversion的constructor,在大部分情况下,都应该使用explicit关键字,从而避免implicit type conversion引起的麻烦。比如:

class Test {
public:
    Test();
    explicit Test(int i);
    explicit Test(float k);
    explicit Test(int i, float k = 20.5);
    explicit Test(int i = 10, float k = 20.5);
};

上面是几种比较常见的情况,其他的情况需要大家自己判断决定。

==================== Operator Overloading ====================

对于大多数的leaf class来说,只有operator overloading能成为Class的friends。通常为了效率考虑,下列的operator都是可以通过friend来实现:

+ - * / 等2元operator

几乎所有的一元operator都应该是member function.

如果data member中有pointer,那么就必须提供 copy constructor和 operator=.

==================== const & reference ====================

正如第一章所讲到的,尽量使用const 来保证你的类的使用者不犯错误,尽量使用reference来提高程序的效率。

 

==================== static data member ====================

如果使用了static data member, 那么需要注意的是,尽量附初值,并且尽量不要在constructor和初始化列表(initialize list)中使用,因为C++ standard并没有规定static data member应该在什么时候被初始化。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值