实现一个无法被继承的C++类

前言:

C++11标准中新增了两个“准关键字”final, override,
其中,final: 用于标识类或成员函数,禁止子类继承或重写覆盖/隐藏;
override: 用于标识虚函数,明确地表示子类成员函数重写覆盖了父类的同名函数,而不是重载(overload);
重写覆盖:子类对父类的重写覆盖要求的是函数签名一样,包括返回值,且为虚函数,与重载不一样;

类成员函数的重载(overload) 、 重写隐藏 、 和重写覆盖(override)区别

注:
参考资料来源:http://blog.csdn.net/dazhong159/article/details/7844369
注: 原文单纯的区分重载、隐藏、和覆盖,有点混乱,这里我做了个人的注解:
成员函数中分为`重载(overload`)和`重写(override)`,
    而重写包括重写隐藏, 重写覆盖(常说override更多指的是这个)


类成员函数的重载(overload)、重写隐藏、和重写覆盖(override)区别
-----------------------------------------

    a.成员函数被重载(overload)的特征:
     (1)相同的范围(在同一个类中);
     (2)函数名字相同;
     (3)参数不同;
     (4virtual关键字可有可无。
     (5)对(1)的补充:不同类中形成的两种重载(overload):
         5.1 如下代码: 
// 5.1
class B 
{
public:
   virtual void f(short) {std::cout << "B::f" << std::endl;}
};

class D : public B
{
public:
   virtual void f(int) {std::cout << "D::f" << std::endl;}
};

// 5.2
class B 
{
public:
   virtual void f(int) const {std::cout << "B::f " << std::endl;}
};

class D : public B
{
public:
   virtual void f(int) {std::cout << "D::f" << std::endl;}
};


    b.重写覆盖(override)是指派生类函数覆盖基类函数,特征是:
     (1)不同的范围(分别位于派生类与基类);
     (2)函数名字相同;
     (3)参数相同;整个函数签名一样;
     (4)基类函数必须有virtual关键字。

   c.重写“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
     (1)如果派生类的函数与基类的函数同名,但是参数不同。
        此时,不论有无virtual关键字,基类的函数将被隐藏
       (注意别与重载混淆)。但注意如果加上virtual则
       通过父类型指针或引用访问不用类名::的形式访问也可以访问;
       如下例:
     (2)如果派生类的函数与基类的函数同名,并且参数也相同,
         但是基类函数没有virtual关键字。此时,
         基类的函数被隐藏(注意别与覆盖混淆)
// 重写隐藏示例       
#include <iostream>
#include <cstring>

using namespace std;

class A
{
public:
    void hello(){cout<<"A hello"<<endl;}
    virtual void hello(int i){cout<<"A hello int"<<endl;}
};

class B:public A
{
public:
    // B中的hello()隐藏了A中的hello(int)
    void hello() const{cout<<"B hello"<<endl;} 
};

// const导致重载时的隐藏问题,解决办法,在B中重新定义hello(int)函数
int main()
{
    B b;
    b.hello();
    // b.hello(1); // error
    b.A::hello(1);  // 这种情况得通过类名::的形式访问

    A *p = new B;
    // 通过父类指针类型来访问还是正确,这种情况不用类名::的形式访问
    p->hello(2); 
}        
// 注意分清楚重载(overload)和重写隐藏以及重写覆盖(override)
// 重写隐藏:
    class A
    {
    public:
        void test(){};
    }
    class B : public A
    {
    public:

    /* 
    因为不是虚函数,同名则会导致重写隐藏父类的,这是要调用子类中
    要调用父类的test()必须通过A::test();的形式进行调用;
    */
        void test(int i){} 
    };


    /*
    另外注意:
        一个函数中是否有const修饰函数也会造成重载,但不属于重写隐藏
        或重写覆盖,是两个不同的函数, 如下例:
    */
    class B
    {
    public:
        virtual void test()const{};
    };
    class D : public B
    {
    public:
        virtual void test(){}; 
    /*
    这个函数没有const修饰,因此这个函数尽管有多态的性质,
    但是是属于两个函数,这是因为成员函数的隐形参数this的原因:
    一个是this,一个是const this的类型;
    */
    };
// final的用法: 用于标识类或成员函数,禁止子类继承或重写
// 具体是禁止重写隐藏还是重写覆盖得看成员函数是否有virtual关键字修饰
// 1, final 修饰类
// 用final修饰,意味着继承该类会导致编译错误
class Hello final 
{
    ...
}

/*
 2, final 修饰成员方法,可以是虚函数,也可以是普通成员函数,
 虚函数则表示不允许子类重写覆盖,但经测试证明仍允许重写隐藏,
 普通成员函数则表示不允许子类重写隐藏:
*/
class A
{
public:
    virtual void f(int) final {}
};

class B : public A
{
public:
    // virtual void f(int){} // 这样将会导致错误,因为父类声明了该虚函数为final,即表示不允许子类重写覆盖
    virtual void f(){} // 正确,这个函数与上面那个函数是互相独立的,不被认为是重写覆盖, 这种情况要好好注意
    virtual void f(double){} // 正确,原因同上
};
// 2 override的用法
// override只能用于标识虚函数,不能是普通成员函数, 比如:
class A
{
public:
    virtual void f(int){}
    void g(){};
};

class B : public A
{
public:
    /*
        明确表示这个函数为的是重写覆盖父类的f(int);虚函数的, 
        如果可能发生错误导致可能的重写隐藏的话编译时就报错
    */
    virtual void f(int) override {} 

    /*
    像这种形式就会导致编译时报错,因为这样不是重写覆盖了,
    而是重写隐藏,重写覆盖要求函数签名是一样的;
    */
    // virtual void f() override {} 

    /*
    错误,因为override只能用于修饰虚函数,g();不是虚函数,
    不可以使用override;
    */
    // void g() override {} 

};

居然看到了上面的final关键字可以修饰类,避免它被继承,那么我们思考一下,如果让我们来实现一个不能被用于继承的类应该怎么实现呢?

正文:

首先得知道的知识:根据虚继承的特性,虚基类的构造函数由最终的子类负责构造,即虚基类子对象由最派生类的构造函数通过调用虚基类构造函数来进行初始化,所谓 最派生类, 假设类A派生出类B,同时类B派生出类C,类C派生出类D。。。那么最后一个被派生出来的类(比如说是Z)就被称为最派生类,也就是最后派生类的意思。
C++中存在继承的类实例的创建过程的构造函数调用顺序是父类的构造函数 -> 其它类的对象成员变量构造函数 -> 自己的构造函数 的调用顺序,因此想到要想一个类不被继承,那么可以将这个类的构造函数和析构函数声明为private属性的成员,这样子类如果在继承的时候想要进行创建对象就无法调用父类的私有成员导致对象创建失败,因此这种方法有点想简单单例模式的实现,代码如下:

class NotInherit
{
private:
    NotInherit(){ }
    ~~NotInherit(){ }
    static NotInherit *instance;
public:
    static NotInherit* getInstance()
    {
        if( NULL == instance )
        {
            instance = new NotInherit;
        }
        return instance;
    }
    static void freeInstance()
    {
        if( NULL != instance )
        {
            delete instance;
        }
    }
};

// 静态成员变量在外部重新定义
NotInherit* NotInherit::instance = NULL; 

int main()
{
    NotInherit* instance = NotInherit::getInstance();
    NotInherit::freeInstance();

    /*
    这个时候这种方法释放资源是错误的,因为析构函数声明为private
    */
    // delete instance; 
}

接下来,我们想想,友元也不能被继承,那么我们可以通过定义友元函数来获取类的实例:

class NotInherit
{
private:
    NotInherit(){}
public:
    /*
    这个时候析构函数要声明为public,否则无法在外部delete,
    而造成资源泄露;
    */
    ~NotInherit(){} 
    friend NotInherit* getInstance();
};

NotInherit* getInstance()
{
    NotInherit *instance = new NotInherit;
    return instance;
}

int main()
{
    NotInherit* instance = getInstance();

    /*
    动态申请的资源要delete否则会造成资源泄露,同时不会调用析构函数
    */
    delete instance; 
}

以上这个类不能被继承,并且没有只能有一个实例的限制,但还是没法想final修饰的类那样可以像普通类那样进行类实例的创建;
于是第三种方法产生了: 使用虚继承,原因就是前面所说的预先要了解的知识:

class NotInherit
{
private:
    NotInherit(){}
    ~NotInherit(){}

    friend class SingleClass;
};

/*
一定要理清楚虚继承的作用,在这里并不是为了解决可能的
    菱形继承(即多继承)二义性的作用
*/
class SingleClass : virtual NotInherit 
{
public:
    SingleClass(){}
    ~SingleClass(){}
};

/*
 注意: 如果上面的继承不是虚继承则以下代码正确,还是可以被继承的,
 但是virtual继承,SingleClass就不可以再被继承了,原因在前面
 */
//class Test : public SingleClass
//{

//};

int main()
{
    SingleClass s ;
    SingleClass *p = new SingleClass;
    delete p;
}

至此,实现一个无法被继承但是可以当做普通的类一样进行创建对象的类已经实现好,以上代码均经过测试通过;

小结:

1:虚继承的虚基类的构造函数调用时机;
2:构造函数声明为私有的好处,以及重温一下单例模式,这篇文章的单例模式实现的不好,
    仅用作为实现无法被继承的类示例而已;
3:final的作用:修饰类或成员函数

    修饰类时: 表示本类禁止被继承;
    修饰成员函数:
        virtual成员函数:表示不允许子类重写覆盖,但可以重写隐藏
        非virtual成员函数:表示不允许子类重写隐藏;

4:override:用于标示虚函数,明确说明这是重写覆盖父类的同名函数,避免造成重写隐藏的情况;

review:

本文与2015年09月09日再次编辑之前对override和overload的理解误区,添加了关于重载和重写方面的理解和总结,让本文看起来有些啰嗦,另外关于重载(overload),重写覆盖,重写隐藏的区别已整理到另一篇博文,地址在下方,欢迎指正;
http://blog.csdn.net/u013777351/article/details/48316177

对文章总结的内容有任何疑问或错误的,欢迎讨论和指正,谢谢;

参考资料:
http://blog.csdn.net/lazy_tiger/article/details/2224899
http://blog.csdn.net/lazybin/article/details/8372793
http://my.oschina.net/xlplbo/blog/343242

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值