从基类指针转换成派生类指针到Is A和Has A的使用
一.问题背景
最近在看基类指针转换成派生类指针的问题,看到一个帖子讨论的挺有意思(https://bbs.csdn.net/topics/330009840).今天花时间看了下.发现有了一些收获,不过也存在一些困惑,现记录在这里,以便以后能够有据可查.
问题大概是这样,楼主想要继承第三方库中的类,在派生类中增加新的方法.在这个过程中没有修改原类,楼主想要通过将基类指针转为派生类指针的方法来实现调用新方法.但他看到有论述说"不能够将基类指针转化为派生类指针(实际上如果此时的基类指向派生类对象的话,可以发生这种转换,我的批注)",故在论坛里对这一问题进行了交流.(我存在的疑惑:既然他可以使用子类来调用新方法,干嘛还要使用基类呢?)
当时的代码如下:
(这里实际上实现的功能是修改m_passed,注意尽管基类中存在有参构造函数,但他只能在初始化的时候指定m_passed的值,而不能在过程中去修改它的值.)
class BaseClass { public: int getPassed() { return m_passed; } BaseClass (int passsed):m_passed(passsed) {} protected: int m_passed; }; class ChildBaseClass: public BaseClass { public: ChildBaseClass():BaseClass(0) {} void setPassed(int passed) {m_passed = passed;} }; int main(int argc, char *argv[]) { ChildBaseClass child; cout << child.getPassed() << endl; child.setPassed(1); cout << child.getPassed() << endl; BaseClass base(0); cout << base.getPassed() << endl; ChildBaseClass *pchild = (ChildBaseClass *) &base; pchild->setPassed(1); cout << base.getPassed() << endl; return 0; }
2.问题探讨:
1.关于这个问题,我在<C++ Primer>习题中见到过相关的论述:
这里的意思是,如果只是添加新的成员函数,可以通过基类转换为派生类的方式来访问.(不过需要用dynamic_cast来保证安全性)
我估计大概的使用套路是这样的:
if(Derived *d = dynamic_cast<Derived *>(p)) { d->fun(); } else { cerr << "调用子类新方法时出错" << endl; }
修改后的程序如下所示:
注意:
1.dynamic_cast使用时,需要保证基类存在虚函数
2.dynamic_cast转换成功需要保证基类指针指向子类对象
class BaseClass { public: virtual void vf() //dynamic_cast使用时,需要保证基类存在虚函数 { } int getPassed() { return m_passed; } BaseClass(int passsed) :m_passed(passsed) {} protected: int m_passed; }; class ChildBaseClass : public BaseClass { public: ChildBaseClass() :BaseClass(0) {} void setPassed(int passed) { m_passed = passed; } }; int main(int argc, char *argv[]) { ChildBaseClass child; cout << child.getPassed() << endl; child.setPassed(1); cout << child.getPassed() << endl; BaseClass *pb = new ChildBaseClass(); cout << pb->getPassed() << endl; if (ChildBaseClass *pchild = dynamic_cast<ChildBaseClass *> (pb)) { pchild->setPassed(1); } else { cerr << "dynamic_cast转换失败" << endl; } cout << pb->getPassed() << endl; return 0;
2.论坛中"arong1234"认为楼主的那种方式存在隐患,如果以后子类中添加了成员变量的话,可能会导致问题;再者应该用has-a的模式去扩展类,去代替is-a的模式.
这种模式的特点是将底层库的指针作为扩展类的成员变量,通过这个指针来使用底层库的方法.
楼主的实现:
class BaseClass { public: int getPassed() { return m_passed; } BaseClass(int passsed) :m_passed(passsed) {} protected: int m_passed; }; class WrapperBaseClass { public: WrapperBaseClass(BaseClass * pBase):m_pBase(pBase) //这种方式是外面把BaseClass指针传进来,给到m_pBase { } void setPassed(int passed) { BaseClass *pBase = new BaseClass(passed); *m_pBase = *pBase; delete pBase; } BaseClass& getBase() { return *m_pBase; } BaseClass* get() { return m_pBase; } private: WrapperBaseClass(const WrapperBaseClass& other) {/* 不允许复制 */ } protected: BaseClass *m_pBase; }; int main(int argc, char *argv[]) { BaseClass base(0); cout << base.getPassed() << endl; WrapperBaseClass wrap(&base); //注意创建对象的写法和创建指针时的写法. WrapperBaseClass *pWrap = new WrapperBaseClass(&base); wrap.setPassed(1); cout << base.getPassed() << endl; cout << wrap.getBase().getPassed() << endl; return 0; }
自己想到的方式:
class BaseClass { public: BaseClass() { m_passed = 0; } int &getPassed() { return m_passed; } BaseClass(int passsed) :m_passed(passsed)//有参构造函数 { } private: int m_passed; }; //Wrapper类实现方式1 class BaseClassWrapper { public: BaseClassWrapper() { m_pBase = new BaseClass(); //这里是自己在构造函数中创建对象 } ~BaseClassWrapper() { if (m_pBase) { delete m_pBase; m_pBase = NULL; } } BaseClass *GetBasePtr() { return m_pBase; } BaseClass GetBaseObj() { return *m_pBase; } private: BaseClass *m_pBase; public: void SetPassed(int passed) //作用:修改基类中的m_passed的值. { BaseClass *pb = new BaseClass(passed); *m_pBase = *pb; //对象的赋值操作.不能够用指针赋值,因为下面马上要delete掉pb了. delete pb; } }; //Wrapper类实现方式2 //class BaseClassWrapper //{ //public: // BaseClassWrapper() // { // m_pBase = new BaseClass(); // } // ~BaseClassWrapper() // { // if (m_pBase) // { // delete m_pBase; // m_pBase = NULL; // } // } // BaseClass *GetBasePtr() // { // return m_pBase; // } // // BaseClass GetBaseObj() // { // return *m_pBase; // } //private: // BaseClass *m_pBase; // //public: // void SetPassed(int passed) //封装好的接口,通过成员变量m_pBase来修改基类中的m_passed的值. // { // m_pBase->getPassed() = passed; //注意这里的getPassed()需要为引用类型,否则它就是getPassed()就是右值. // } //}; int main() { BaseClassWrapper bcw; cout << "初始化时m_passed的值" << bcw.GetBasePtr()->getPassed() << endl; bcw.SetPassed(1); cout << "修改后m_passed的值" << bcw.GetBasePtr()->getPassed() << endl; return 0; }
对比楼主和我在BaseClassWrapper构造函数的区别,一个是有参构造函数,把外边的指针传过去;另一个是无参构造,在构造函数里面去创建指针.