美文网首页工作生活
C++虚继承实现链式调用的抽象

C++虚继承实现链式调用的抽象

作者: 江海小流 | 来源:发表于2019-07-04 21:39 被阅读0次

    链式调用

    认为链式调用是一种代码风格,在一些情况下能够使代码容易理解。比如:

    Student studentFromChina;
    studentFromChina.setName("Alice");
    studentFromChina.setAge(14);
    studentFromChina.setGrade('A');
    
    // 链式
    studentFromChina.setName("Alice")
                    .setAge(14)
                    .setGrade('A')
    

    可以看到,如果变量比较复杂,通过链式的风格,能够帮助我们把注意力集中到关键的地方。

    单个类的情况

    如果只涉及单个类,使用C++来实现支持链式调用的类还是非常简单的。如

    class BytesReader {
      private:
        char* data;
        int cursor;
      public:
        BytesReader(char *data): data(data), cursor(0) {};
        BytesReader* move(int step) {
            this->cursor += step;
            return this;
        }
        BytesReader* reset();
        BytesReader* read_int(int &val, int num_of_bytes);
        ...
    };
    

    BytesReader 能够提供在一个 char 数组上随机如取 int范围的值的功能。每次函数调用完会将自身返回就可以用于链式调用,写出链式调用的风格,如:

    reader->read_int(a, 4)->read_int(b, 4)->read_int(c, 4);
    

    多个类的情况

    假设还有一个类如下:

    class BytesWriter {
      private:
        char* data;
        int cursor;
      public:
        BytesWriter(char *data): data(data), cursor(0) {};
        BytesWriter* move(int step) {
            this->cursor += step;
            return this;
        }
        BytesWriter* reset();
        BytesWriter* write_int(int &val, int num_of_byte);
        ...
    };
    

    对于这个类,理所当然想把他们公用的部分抽取出来,如move, reset

    所以,我们可以试着写出如下类来:

    class BytesHandler {
       protected:
         char* data;
         int cursor;
      public:
        BytesHandler(char* data): data(data), cursor(0) {}
        BytesHandler* move();
        BytesHandler* reset();
    };
    

    如果把这个类当成基类,那么就可以通过继承的方式派生出 BytesReader, BytesWriter来了

    class BytesReader: public BytesHandler {...};
    class BytesWriter: public BytesHandler {...};
    

    但是,这样子真的可以吗?对于如下的调用,真的会达到预期的效果吗?

    reader->move(8)->read_int(a, 4)->read_int(b, 4);
    

    对于 move 这个函数来说,可以发现它的返回值时 BytesHandler*,然而 BytesHandler 是没有 read_int 的方法的,所以上述代码显然会报错。

    如果想要把 move 的方法放到父类里面,且返回值是派生类,暂时只想到了用模板的方法类做,也就是说,move函数的返回值是 T *,当BytesReader继承父类时,必须制定这个T = BytesReader,也就是它本身,这样就可以达到我们的目的了。

    template<class T>
    class BytesHandler {
      private:
        char *data;
        char cursor;
      public:
        BytesHandler(char *data):data(data), cursor(0) {};
        T* move(int steps) {
          this->cursor += steps;
          return (T*) this; // 后面找资料的时候,发现一般用 static_cast<T*>(this)来做
        }
        T* reset();
    };
    
    class BytesReader: public BytesHandler<BytesReader> {
      public:
        BytesReader(char *data): BytesHandler<BytesReader>(data) {};
        BytesReader* read_int(int &val, int num_of_bits);
    };
    
    class BytesWriter: public BytesHandler<BytesWriter> {
      public:
        BytesWriter(char *data): BytesHandler<BytesWriter>(data) {};
        BytesWriter* read_int(int &val, int num_of_bits);
    };
    

    多继承

    如果想要一个类既有BytesReaderBytesWriter的功能,那么是否可以直接用一个类继承 BytesReaderBytesWriter呢?

    class BytesReadWriter: public BytesReader, 
                           public BytesWriter {...};
    

    然而,这样是不行的,这样会导致几个问题:

    1. 第一个是BytesWriter,BytesReader都有一份BytesHandler的副本,一般来说,多继承用虚继承来防止出现这个问题。那么,BytesWriter 和 BytesReader 与 BytesHandler之间必须也是虚继承的关系。然而,如果一旦把继承关系改成虚拟继承,

      T* move(int steps) {
        this->cursor += steps;
        return (T*) this;
      }
      

      就会报错,error: cannot convert from pointer to base class ‘BytesHandler<BytesWriter>’ to pointer to derived class ‘BytesWriter’ because the base is virtual
      (原因待查明)

      有一个小的技巧可以解决这个问题。

      template<class T>
      class BytesHandler {
        protected:
          Bytes* data;
          int cursor;
      
        public:
          BytesHandler() {};
          BytesHandler(Bytes* data): data(data), cursor(0) {};
          T* move(int steps) {
            this->cursor += steps;
            return this->self();
          }
          T* reset();
          virtual T* self() = 0;
      };
      

      在派生来中,实现虚函数 self 来返回自身的指针,作为虚函数,它是动态绑定的,因此是在运行的时候来决定运行具体代码的片段的,就可以绕过这个错误。

      也可以用 dynamic_cast<T*> 来替换 (T*)

      最后继承的代码改成:

      class BytesReader: virtual public BytesHandler<BytesReader> {
        public:
          BytesReader() {};
          BytesReader(Bytes *data): BytesHandler<BytesReader>(data) {};
          BytesReader* read_int(int& val, int num);
          BytesReader* read_bytes(Bytes *&bytes, int num);
          BytesReader* self();
      };
      
      class BytesWriter: virtual public BytesHandler<BytesWriter> {
        public:
          BytesWriter() {};
          BytesWriter(Bytes* data): BytesHandler<BytesWriter>(data) {};
          BytesWriter* write_int(int val, int num);
          BytesWriter* write_bytes(Bytes *bytes, int num);
          BytesWriter* self();
      };
      
      class BytesReaderWriter: public BytesReader,
                               public BytesWriter,
                               public BytesHandler<BytesReaderWriter> {
        public:
          BytesReaderWriter() {};
          BytesReaderWriter(Bytes *data): BytesHandler<BytesReaderWriter>(data) {};
          BytesReaderWriter* self();
      };
      

      然而,改成这样,代码还是无法运行的。原因在于还是有多份副本。为什么用虚继承,还是没有解决多份副本的问题?

      原因应该是,BytesHandler<BytesReader>BytesHandler<BytesReader>虽然都是BytesHandler,但是实际上它们是不同的类,因此可以再做一个抽象类出来;

      class BaseBytesHandler {
        protected:
          char* data;
          int cursor;
      
        public:
          BaseBytesHandler() {};
          BaseBytesHandler(char* data): data(data), cursor(0) {};
          virtual ~BaseBytesHandler() {};
      };
      
      template<class T>
      class BytesHandler: virtual public BaseBytesHandler {
        public:
          BytesHandler() {};
          BytesHandler(char* data): BaseBytesHandler(data) {};
          T* move(int num);
          T* reset();
          virtual T* self() = 0;
      };
      
      
      class BytesReader: public BytesHandler<BytesReader> {
        public:
          BytesReader() {};
          BytesReader(char *data): BaseBytesHandler(data) {};
          BytesReader* read_int(int& val, int num);
          BytesReader* read_bytes(Bytes *&bytes, int num);
          BytesReader* self();
      };
      
      class BytesWriter: public BytesHandler<BytesWriter> {
        public:
          BytesWriter() {};
          BytesWriter(char* data): BaseBytesHandler(data) {};
          BytesWriter* write_int(int val, int num);
          BytesWriter* write_bytes(Bytes *bytes, int num);
          BytesWriter* self();
      };
      
      class BytesReaderWriter: virtual public BytesReader,
                           virtual public BytesWriter,
                           virtual public BytesHandler<BytesReaderWriter> {
        public:
          BytesReaderWriter() {};
          BytesReaderWriter(char *data): BaseBytesHandler(data) {};
          BytesReaderWriter* self();
      };
      

      这样,虚继承公用的基类就是 BaseBytesHandler了,就可以解决上述多份副本的问题了。

    2. 第二个问题,BytesReadWriter 继承自 BytesReaderBytesWriter,而它们都有movereset 函数,所以BytesReadWriter 需要显式的申明调用那个。

      readwriter->BytesHandler<BytesReaderWriter>::reset();
      
    3. 第三个问题,BytesReadWriter 的实例,调用read_int函数后返回的是BytesReader的指针,就不能调用write_int 的方法了。

    针对这些问题,有一个解决方案:使用CRTP的方式,来组织这些类。(CRTP是什么,可以参考后文的链接)如:

    template<class T>
    class Handlable {
      public:
        T* move(int steps) {
          self()->cursor += steps;
          return self();
        }
        T* reset();
        T* self() {
          return static_cast<T*>(this);
        }
      private:
        Handlable() {};
        friend T;
    };
    
    template<class T>
    class Writerable {
        public:
        T* write_int(int val, int num_of_bytes);
        T* write_bytes(Bytes *bytes, int num_of_bytes);
        T* self();
      private:
        Writerable() {};
        friend T;
    };
    

    通过

    class BytesWriter: public Handlable<BytesWriter>, public Writerable<BytesWriter> {...};
    

    这样的形式,从而可以解决上述的三个问题。对于第一个问题,上述没有棱形继承,所以也不会有多份副本存在导致二义性的问题,如果把self再单独拿出来,做一层继承,也可以用虚继承的方式来解决多份副本的问题(如果用虚继承,self函数中的static_cast 需要用dynamic_cast 来替代)。对于第二个问题,每次函数调用完后可以返回self函数的返回值,而这个函数返回的是最终模板T的指针,而这个T是唯一的,也就是类本身,所以不会有二义性问题。对于第三个问题,也是因为返回的T的指针,可以解决。

    涉及的解决方案
    参考博客
    CRTP
    mixin & crtp

    CRTP 代码整理

    common.h

    # ifndef __DATA_TYPE_H__
    # define __DATA_TYPE_H__
    
    typedef unsigned char byte;
    
    const int BLOCK_SIZE = 4096;
    const int BLOCK_HEADER_SIZE = 4 * sizeof(int);
    
    const int BYTE_ARRAY_INITIAL_SIZE = 512;
    const int STRING_INTIAL_SIZE = 512;
    
    class Bytes {
        private:
            byte* data;
            int capacity;
            int length;
        public:
            Bytes();
            Bytes(byte* buffer, int num);
            Bytes(byte fill, int num);
            ~Bytes();
    
            byte &operator[] (int index);
            const byte &operator[] (int index) const;
            bool operator== (const Bytes &other) const;
            bool operator!= (const Bytes &other) const;
            Bytes* slice(int);
            int len() const;
    };
    # endif
    

    handler.h

    # ifndef __READER_H__
    # define __READER_H__
    
    # include "common.h"
    # include "assert.h"
    
    template<class T>
    class Functionality {
        public:
            virtual T* self() {return dynamic_cast<T*>(this);}
    };
    
    template<class T>
    class Handlable: virtual public Functionality<T> {
        public:
            T* move(int steps);
            T* reset();
        private:
            Handlable() {};
            friend T;
    };
    
    template<class T>
    class Readable: virtual public Functionality<T> {
        public:
            T* read_int(int& val, int num_of_bytes);
            T* read_bytes(Bytes *&bytes, int num_of_bytes);
        private:
            Readable() {};
            friend T;
    };
    
    template<class T>
    class Writerable: virtual public Functionality<T> {
        public:
            T* write_int(int val, int num_of_bytes);
            T* write_bytes(Bytes *bytes, int num_of_bytes);
        private:
            Writerable() {};
            friend T;
    };
    
    class BaseBytesHandler {
        public:
            Bytes* data;
            int cursor;
        
        public:
            BaseBytesHandler(Bytes* data): data(data), cursor(0) {};
            virtual ~BaseBytesHandler() {};
    };
    
    class BytesReader: public BaseBytesHandler, 
                       public Handlable<BytesReader>,
                       public Readable<BytesReader> {
        public:
            BytesReader(Bytes *data): BaseBytesHandler(data) {};
    };
    
    class BytesWriter: public BaseBytesHandler, 
                       public Handlable<BytesWriter>,
                       public Writerable<BytesWriter> {
        public:
            BytesWriter(Bytes* data): BaseBytesHandler(data) {};
    };
    
    class BytesReaderWriter: public BaseBytesHandler,
                             public Handlable<BytesReaderWriter>,
                             public Readable<BytesReaderWriter>,
                             public Writerable<BytesReaderWriter> {
        public:
            BytesReaderWriter(Bytes* data): BaseBytesHandler(data) {};
    };
    
    # endif
    

    handler.cpp

    # include "handler.h"
    # include "assert.h"
    
    
    template<class T>
    T* Handlable<T>::move(int steps) {
        assert(0 <= (this->self())->cursor + steps && (this->self())->cursor + steps <= (this->self())->data->len());
        (this->self())->cursor += steps;
        return (this->self());
    }
    
    template<class T>
    T* Handlable<T>::reset() {
        (this->self())->cursor = 0;
        return (this->self());
    }
    
    template<class T>
    T* Readable<T>::read_int(int &val, int num) {
        assert(0 < num && num <= 4);
        val = 0;
        for (int i = 0, base = 1; i < num; i++, base *= 256) {
            val += base * (int)((*(this->self())->data)[(this->self())->cursor]);
            (this->self())->cursor ++;
        }
        return (this->self());
    }
    
    template<class T>
    T* Readable<T>::read_bytes(Bytes *&byte_array, int num) {
        assert((this->self())->cursor + num <= (this->self())->data->len());
        byte* bytes = new byte[num];
        for (int i = 0; i < num; i++) {
            bytes[i] = (*(this->self())->data)[(this->self())->cursor];
            (this->self())->cursor ++;
        }
        byte_array = new Bytes(bytes, num);
        delete[] bytes;
        return (this->self());
    }
    
    
    template<class T>
    T* Writerable<T>::write_int(int val, int num) {
        assert((this->self())->cursor + num <= (this->self())->data->len());
        for (int i = 0, tmp = val; i < num; i++, tmp /= 256) {
            (*(this->self())->data)[(this->self())->cursor] = tmp % 256;
            (this->self())->cursor ++;
        }
        return (this->self());
    }
    
    template<class T>
    T* Writerable<T>::write_bytes(Bytes* bytes, int num) {
        assert((this->self())->cursor + num <= (this->self())->data->len());
        assert(num <= bytes->len());
        for (int i = 0; i < num; i++) {
            (*(this->self())->data)[(this->self())->cursor] = (*bytes)[i];
            (this->self())->cursor ++;
        }
        return (this->self());
    }
    
    
    template class Readable<BytesReader>;
    template class Readable<BytesReaderWriter>;
    template class Writerable<BytesWriter>;
    template class Writerable<BytesReaderWriter>;
    template class Handlable<BytesReader>;
    template class Handlable<BytesWriter>;
    template class Handlable<BytesReaderWriter>;
    

    相关文章

      网友评论

        本文标题:C++虚继承实现链式调用的抽象

        本文链接:https://www.haomeiwen.com/subject/smqdhctx.html