cocos2d-x js绑定的时候,C++中的类是多继承的时候该怎么办?-问答-阿里云开发者社区-阿里云

开发者社区> 问答> 正文

cocos2d-x js绑定的时候,C++中的类是多继承的时候该怎么办?

a123456678 2016-03-05 11:25:44 1307

cocos2d-x js绑定的时候,C++中的类是多继承的时候该怎么办?
我这里的C++类继承了两个类,每一个类都有实体方法。
当parent proto指定其中一个类的时候,就调用不到另外一个父类的方法了。
求大牛解救

JavaScript C++
分享到
取消 提交回答
全部回答(1)
  • a123456678
    2019-07-17 18:53:06

    触及 multiple inheritance (MI)(多继承)的时候,C++ 社区就会鲜明地分裂为两个基本的阵营。一个阵营认为如果 single inheritance (SI)(单继承)是有好处的,multiple inheritance(多继承)一定更有好处。另一个阵营认为 single inheritance(单继承)有好处,但是多继承引起的麻烦使它得不偿失。在本文中,我们的主要目的是理解在 MI 问题上的这两种看法。

      首要的事情之一是要承认当将 MI 引入设计领域时,就有可能从多于一个的 base class(基类)中继承相同的名字(例如,函数,typedef,等等)。这就为歧义性提供了新的时机。例如:

    class BorrowableItem { // something a library lets you borrow
    public:
     void checkOut(); // check the item out from the library
     ..
    };

    class ElectronicGadget {
    private:
     bool checkOut() const; // perform self-test, return whether
     ... // test succeeds
    };

    class MP3Player: // note MI here
    public BorrowableItem, // (some libraries loan MP3 players)
    public ElectronicGadget
    { ... }; // class definition is unimportant

    MP3Player mp;

    mp.checkOut(); // ambiguous! which checkOut?

       注意这个例子,即使两个函数中只有一个是可访问的,对 checkOut 的调用也是有歧义的。(checkOut 在 BorrowableItem 中是 public(公有)的,但在 ElectronicGadget 中是 private(私有)的。)这与 C++ 解析 overloaded functions(重载函数)调用的规则是一致的:在看到一个函数的是否可访问之前,C++ 首先确定与调用匹配最好的那个函数。只有在确定了 best-match function(最佳匹配函数)之后,才检查可访问性。这目前的情况下,两个 checkOuts 具有相同的匹配程度,所以就不存在最佳匹配。因此永远也不会检查到 ElectronicGadget::checkOut 的可访问性。

      为了消除歧义性,你必须指定哪一个 base class(基类)的函数被调用:

    mp.BorrowableItem::checkOut(); // ah, that checkOut...

      当然,你也可以尝试显式调用 ElectronicGadget::checkOut,但这样做会有一个 "you're trying to call a private member function"(你试图调用一个私有成员函数)错误代替歧义性错误。

       multiple inheritance(多继承)仅仅意味着从多于一个的 base class(基类)继承,但是在还有 higher-level base classes(更高层次基类)的 hierarchies(继承体系)中出现 MI 也并不罕见。这会导致有时被称为 "deadly MI diamond"(致命的多继承菱形)的后果。

    class File { ... };
    class InputFile: public File { ... };
    class OutputFile: public File { ... };
    class IOFile: public InputFile,
    public OutputFile
    { ... };

       在一个“在一个 base class(基类)和一个 derived class(派生类)之间有多于一条路径的 inheritance hierarchy(继承体系)”(就像上面在 File 和 IOFile 之间,有通过 InputFile 和 OutputFile 的两条路径)的任何时候,你都必须面对是否需要为每一条路径复制 base class(基类)中的 data members(数据成员)的问题。例如,假设 File class 有一个 data members(数据成员)fileName。IOFile 中应该有这个 field(字段)的多少个拷贝呢?一方面,它从它的每一个 base classes(基类)继承一个拷贝,这就暗示 IOFile 应该有两个 fileName data members(数据成员)。另一方面,简单的逻辑告诉我们一个 IOFile object(对象)应该仅有一个 file name(文件名),所以通过它的两个 base classes(基类)继承来的 fileName field(字段)不应该被复制。

      C++ 在这个争议上没有自己的立场。它恰当地支持两种选项,虽然它的缺省方式是执行复制。如果那不是你想要的,你必须让这个 class(类)带有一个 virtual base class(虚拟基类)的数据(也就是 File)。为了做到这一点,你要让从它直接继承的所有的 classes(类)使用 virtual inheritance(虚拟继承):

    class File { ... };
    class InputFile: virtual public File { ... };
    class OutputFile: virtual public File { ... };
    class IOFile: public InputFile,
    public OutputFile
    { ... };

       标准 C++ 库包含一个和此类似的 MI hierarchy(继承体系),只是那个 classes(类)是 class templates(类模板),名字是 basic_ios,basic_istream,basic_ostream 和 basic_iostream,而不是 File,InputFile,OutputFile 和 IOFile。

      从正确行为的观点 看,public inheritance(公有继承)应该总是 virtual(虚拟)的。如果这是唯一的观点,规则就变得简单了:你使用 public inheritance(公有继承)的任何时候,都使用 virtual public inheritance(虚拟公有继承)。唉,正确性不是唯一的视角。避免 inherited fields(继承来的字段)复制需要在编译器的一部分做一些 behind-the-scenes legerdemain(幕后的戏法),而结果是从使用 virtual inheritance(虚拟继承)的 classes(类)创建的 objects(对象)通常比不使用 virtual inheritance(虚拟继承)的要大。访问 virtual base classes(虚拟基类)中的 data members(数据成员)也比那些 non-virtual base classes(非虚拟基类)中的要慢。编译器与编译器之间有一些细节不同,但基本的要点很清楚:virtual inheritance costs(虚拟继承要付出成本)。

      它也有一些其它方面的成本。支配 initialization of virtual base classes(虚拟基类初始化)的规则比 non-virtual bases(非虚拟基类)的更加复杂而且更不直观。初始化一个 virtual base(虚拟基)的职责由 hierarchy(继承体系)中 most derived class(层次最低的派生类)承担。这个规则中包括的含义:

      (1) 从需要 initialization(初始化)的 virtual bases(虚拟基)派生的 classes(类)必须知道它们的 virtual bases(虚拟基),无论它距离那个 bases(基)有多远;

      (2) 当一个新的 derived class(派生类)被加入继承体系时,它必须为它的 virtual bases(虚拟基)(包括直接的和间接的)承担 initialization responsibilities(初始化职责)。

       我对于 virtual base classes(虚拟基类)(也就是 virtual inheritance(虚拟继承))的建议很简单。首先,除非必需,否则不要使用 virtual bases(虚拟基)。缺省情况下,使用 non-virtual inheritance(非虚拟继承)。第二,如果你必须使用 virtual base classes(虚拟基类),试着避免在其中放置数据。这样你就不必在意它的 initialization(初始化)(以及它的 turns out(清空),assignment(赋值))规则中的一些怪癖。值得一提的是 Java 和 .NET 中的 Interfaces(接口)不允许包含任何数据,它们在很多方面可以和 C++ 中的 virtual base classes(虚拟基类)相比照。

      现在我们使用下面的 C++ Interface class(接口类)(参见《C++箴言:最小化文件之间的编译依赖》)来为 persons(人)建模:

    class IPerson {
    public:
     virtual ~IPerson();

     virtual std::string name() const = 0;
     virtual std::string birthDate() const = 0;
    };

       IPerson 的客户只能使用 IPerson 的 pointers(指针)和 references(引用)进行编程,因为 abstract classes(抽象类)不能被实例化。为了创建能被当作 IPerson objects(对象)使用的 objects(对象),IPerson 的客户使用 factory functions(工厂函数)(再次参见 Item 31)instantiate(实例化)从 IPerson 派生的 concrete classes(具体类):

    // factory function to create a Person object from a unique database ID;
    // see Item 18 for why the return type isn't a raw pointer
    std::tr1::shared_ptr makePerson(DatabaseID personIdentifier);

    // function to get a database ID from the user
    DatabaseID askUserForDatabaseID();

    DatabaseID id(askUserForDatabaseID());
    std::tr1::shared_ptr pp(makePerson(id)); // create an object
    // supporting the
    // IPerson interface

    ... // manipulate *pp via
    // IPerson's member
    // functions

      但是 makePerson 怎样创建它返回的 pointers(指针)所指向的 objects(对象)呢?显然,必须有一些 makePerson 可以实例化的从 IPerson 派生的 concrete class(具体类)。

       假设这个 class(类)叫做 CPerson。作为一个 concrete class(具体类),CPerson 必须提供它从 IPerson 继承来的 pure virtual functions(纯虚拟函数)的 implementations(实现)。它可以从头开始写,但利用包含大多数或全部必需品的现有组件更好一些。例如,假设一个老式的 database-specific class(老式的数据库专用类)PersonInfo 提供了 CPerson 所需要的基本要素:

    class PersonInfo {
    public:
     explicit PersonInfo(DatabaseID pid);
     virtual ~PersonInfo();

     virtual const char * theName() const;
     virtual const char * theBirthDate() const;
     ...

    private:
     virtual const char * valueDelimOpen() const; // see
     virtual const char * valueDelimClose() const; // below
     ...
    };

       你可以看出这是一个老式的 class(类),因为 member functions(成员函数)返回 const char*s 而不是 string objects(对象)。尽管如此,如果鞋子合适,为什么不穿呢?这个 class(类)的 member functions(成员函数)的名字暗示结果很可能会非常合适。

      你突然发现 PersonInfo 是设计用来帮助以不同的格式打印 database fields(数据库字段)的,每一个字段的值的开始和结尾通过指定的字符串定界。缺省情况下,字段值开始和结尾定界符是方括号,所以字段值 "Ring-tailed Lemur" 很可能被安排成这种格式:

    [Ring-tailed Lemur]

      根据方括号并非满足 PersonInfo 的全体客户的期望的事实,virtual functions(虚拟函数)valueDelimOpen 和 valueDelimClose 允许 derived classes(派生类)指定它们自己的开始和结尾定界字符串。PersonInfo 的 member functions(成员函数)的 implementations(实现)调用这些 virtual functions(虚拟函数)在它们返回的值上加上适当的定界符。作为一个例子使用 PersonInfo::theName,代码如下:

    const char * PersonInfo::valueDelimOpen() const
    {
     return "["; // default opening delimiter
    }

    const char * PersonInfo::valueDelimClose() const
    {
     return "]"; // default closing delimiter
    }

    const char * PersonInfo::theName() const
    {
     // reserve buffer for return value; because this is
     // static, it's automatically initialized to all zeros
     static char value[Max_Formatted_Field_Value_Length];

     // write opening delimiter
     std::strcpy(value, valueDelimOpen());

     append to the string in value this object's name field (being careful to avoid buffer overruns!)

     // write closing delimiter
     std::strcat(value, valueDelimClose());

     return value;
    }

       有人可能会质疑 PersonInfo::theName 的陈旧的设计(特别是一个 fixed-size static buffer(固定大小静态缓冲区)的使用,这样的东西发生 overrun(越界)和 threading(线程)问题是比较普遍的——参见《C++箴言:必须返回对象时别返回引用》),但是请把这样的问题放到一边而注意这里:theName 调用 valueDelimOpen 生成它要返回的 string(字符串)的开始定界符,然后它生成名字值本身,然后它调用 valueDelimClose。

      因为 valueDelimOpen 和 valueDelimClose 是 virtual functions(虚拟函数),theName 返回的结果不仅依赖于 PersonInfo,也依赖于从 PersonInfo 派生的 classes(类)。

       对于 CPerson 的实现者,这是好消息,因为当细读 IPerson documentation(文档)中的 fine print(晦涩的条文)时,你发现 name 和 birthDate 需要返回未经修饰的值,也就是,不允许有定界符。换句话说,如果一个人的名字叫 Homer,对那个人的 name 函数的一次调用应该返回 "Homer",而不是 "[Homer]"。

      CPerson 和 PersonInfo 之间的关系是 PersonInfo 碰巧有一些函数使得 CPerson 更容易实现。这就是全部。因而它们的关系就是 is-implemented-in-terms-of,而我们知道有两种方法可以表现这一点:经由 composition(复合)(参见《C++箴言:通过composition模拟“has-a”》)和经由 private inheritance(私有继承)(参见《C++箴言:谨慎使用私有继承》)。《C++箴言:谨慎使用私有继承》 指出 composition(复合)是通常的首选方法,但如果 virtual functions(虚拟函数)要被重定义,inheritance(继承)就是必不可少的。在当前情况下,CPerson 需要重定义 valueDelimOpen 和 valueDelimClose,所以简单的 composition(复合)做不到。最直截了当的解决方案是让 CPerson 从 PersonInfo privately inherit(私有继承),虽然 《C++箴言:谨慎使用私有继承》 说过只要多做一点工作,则 CPerson 也能用 composition(复合)和 inheritance(继承)的组合有效地重定义 PersonInfo 的 virtuals(虚拟函数)。这里,我们用 private inheritance(私有继承)。

      但 是 CPerson 还必须实现 IPerson interface(接口),而这被称为 public inheritance(公有继承)。这就引出一个 multiple inheritance(多继承)的合理应用:组合 public inheritance of an interface(一个接口的公有继承)和 private inheritance of an implementation(一个实现的私有继承):

    class IPerson { // this class specifies the
    public: // interface to be implemented
     virtual ~IPerson();

     virtual std::string name() const = 0;
     virtual std::string birthDate() const = 0;
    };

    class DatabaseID { ... }; // used below; details are
    // unimportant

    class PersonInfo { // this class has functions
    public: // useful in implementing
     explicit PersonInfo(DatabaseID pid); // the IPerson interface
     virtual ~PersonInfo();

     virtual const char * theName() const;
     virtual const char * theBirthDate() const;

     virtual const char * valueDelimOpen() const;
     virtual const char * valueDelimClose() const;
     ...
    };

    class CPerson: public IPerson, private PersonInfo { // note use of MI
    public:
     explicit CPerson( DatabaseID pid): PersonInfo(pid) {}
     virtual std::string name() const // implementations
     { return PersonInfo::theName(); } // of the required
     // IPerson member
     virtual std::string birthDate() const // functions
     { return PersonInfo::theBirthDate(); }
    private: // redefinitions of
     const char * valueDelimOpen() const { return ""; } // inherited virtual
     const char * valueDelimClose() const { return ""; } // delimiter
    }; // functions

      在 UML 中,这个设计看起来像这样:

      这个例子证明 MI 既是有用的,也是可理解的。

       时至今日,multiple inheritance(多继承)不过是 object-oriented toolbox(面向对象工具箱)里的又一种工具而已,典型情况下,它的使用和理解更加复杂,所以如果你得到一个或多或少等同于一个 MI 设计的 SI 设计,则 SI 设计总是更加可取。如果你能拿出来的仅有的设计包含 MI,你应该更加用心地考虑一下——总会有一些方法使得 SI 也能做到。但同时,MI 有时是最清晰的,最易于维护的,最合理的完成工作的方法。在这种情况下,毫不畏惧地使用它。只是要确保谨慎地使用它。

      Things to Remember

      ·multiple inheritance(多继承)比 single inheritance(单继承)更复杂。它能导致新的歧义问题和对 virtual inheritance(虚拟继承)的需要。

       ·virtual inheritance(虚拟继承)增加了 size(大小)和 speed(速度)成本,以及 initialization(初始化)和 assignment(赋值)的复杂度。当 virtual base classes(虚拟基类)没有数据时它是最适用的。

      ·multiple inheritance(多继承)有合理的用途。一种方案涉及组合从一个 Interface class(接口类)的 public inheritance(公有继承)和从一个有助于实现的 class(类)的 private inheritance(私有继承)。

    关于虚拟继承的思考
    虚拟继承在一般的应用中很少用到,所以也往往被忽视,这也主要是因为在C++中,多重继承是不推荐的,而一旦离开了多重继承,虚拟继承就完全失去了存在的必要(因为这样只会降低效率和占用更多的空间,实在是一无是处)。
      以下面的一个例子为例:
      #include
      #include
      class CA
      {
       int k; //为了便于说明后面的内存结构特别添加
      public:
       void f() {cout << "CA::f" << endl;}
      };
      class CB : public CA
      {
      };
      class CC : public CA
      {
      };
      class CD : public CB, public CC
      {
      };
      void main()
      {
       CD d;
       d.f();
      }
      当编译上述代码时,我们会收到如下的错误提示:
      error C2385: 'CD::f' is ambiguous
      即编译器无法确定你在d.f()中要调用的函数f到底是哪一个。这里可能会让人觉得有些奇怪,命名只定义了一个CA::f,既然大家都派生自CA,那自然就是调用的CA::f,为什么还无法确定呢?
      这是因为编译器在进行编译的时候,需要确定子类的函数定义,如CA::f是确定的,那么在编译CB、CC时还需要在编译器的语法树中生成CB::f,CC::f等标识,那么,在编译CD的时候,由于CB、CC都有一个函数f,此时,编译器将试图生成两个CD::f标识,显然这时就要报错了。(当我们不使用CD::f的时候,以上标识都不会生成,所以,如果去掉d.f()一句,程序将顺利通过编译)
      要解决这个问题,有两个方法:
      1、重载函数f():此时由于我们明确定义了CD::f,编译器检查到CD::f()调用时就无需再像上面一样去逐级生成CD::f标识了;
      此时CD的元素结构如下:
      --------
      |CB(CA)|
      |CC(CA)|
      --------
      故此时的sizeof(CD) = 8;(CB、CC各有一个元素k)
      2、使用虚拟继承:虚拟继承又称作共享继承,这种共享其实也是编译期间实现的,当使用虚拟继承时,上面的程序将变成下面的形式:
      #include
      #include
      class CA
      {
       int k;
      public:
       void f() {cout << "CA::f" << endl;}
      };
      class CB : virtual public CA
      {
      };
      class CC : virtual public CA
      {
      };
      class CD : public CB, public CC
      {
      };
      void main()
      {
       CD d;
       d.f();
      }
      此时,当编译器确定d.f()调用的具体含义时,将生成如下的CD结构:
      ----
      |CB|
      |CC|
      |CA|
      ----
      同时,在CB、CC中都分别包含了一个指向CA的vbptr(virtual base table pointer),其中记录的是从CB、CC的元素到CA的元素之间的偏移量。此时,不会生成各子类的函数f标识,除非子类重载了该函数,从而达到“共享”的目的。
      也正因此,此时的sizeof(CD) = 12(两个vbptr + sizoef(int));

    0 0
开发与运维
使用钉钉扫一扫加入圈子
+ 订阅

集结各类场景实战经验,助你开发运维畅行无忧

推荐文章
相似问题
推荐课程