C++继承设计:避免成员重复——读写文件描述符类设计问询
好问题!在C++里遇到这种多个类持有重复成员的情况,咱们有几种干净利落的设计方案,我给你逐个拆解清楚:
方案1:提取公共基类(最推荐)
最优雅的方式是把重复的file_descriptor_成员抽离到一个单独的公共基类中,让ReadFd和WriteFd都继承这个基类。这样ReadWriteFd在多重继承这两个类时,就不会出现重复的文件描述符成员了,完全符合单一职责原则。
代码示例:
// 公共基类:只负责管理文件描述符 class FdBase { protected: int file_descriptor_; // 构造函数统一初始化文件描述符,避免重复逻辑 explicit FdBase(int fd) : file_descriptor_(fd) {} }; // 只读类:继承公共基类,专注读操作逻辑 class ReadFd : public FdBase { public: explicit ReadFd(int fd) : FdBase(fd) {} ssize_t read(void* buf, size_t count) { // 直接使用基类的file_descriptor_ return ::read(file_descriptor_, buf, count); } }; // 只写类:继承公共基类,专注写操作逻辑 class WriteFd : public FdBase { public: explicit WriteFd(int fd) : FdBase(fd) {} ssize_t write(const void* buf, size_t count) { // 直接使用基类的file_descriptor_ return ::write(file_descriptor_, buf, count); } }; // 读写类:多重继承ReadFd和WriteFd,自动拥有读写能力,且file_descriptor_仅一份 class ReadWriteFd : public ReadFd, public WriteFd { public: explicit ReadWriteFd(int fd) : FdBase(fd), ReadFd(fd), WriteFd(fd) {} };
这个方案的优势在于结构清晰,每个类只做自己擅长的事:FdBase管文件描述符,ReadFd管读,WriteFd管写,ReadWriteFd直接组合两者能力,没有冗余。
方案2:虚继承解决菱形继承问题
如果不想大幅重构现有的ReadFd和WriteFd类,可以让它们虚继承同一个公共基类。虚继承的特性会让所有虚继承的子类共享基类的同一个实例,这样即使ReadWriteFd多重继承,file_descriptor_也只会存在一份。
代码示例:
class FdBase { protected: int file_descriptor_; explicit FdBase(int fd) : file_descriptor_(fd) {} }; // 虚继承公共基类 class ReadFd : virtual public FdBase { public: explicit ReadFd(int fd) : FdBase(fd) {} ssize_t read(void* buf, size_t count) { return ::read(file_descriptor_, buf, count); } }; // 虚继承公共基类 class WriteFd : virtual public FdBase { public: explicit WriteFd(int fd) : FdBase(fd) {} ssize_t write(const void* buf, size_t count) { return ::write(file_descriptor_, buf, count); } }; // 读写类多重继承,此时FdBase仅被实例化一次 class ReadWriteFd : public ReadFd, public WriteFd { public: explicit ReadWriteFd(int fd) : FdBase(fd), ReadFd(fd), WriteFd(fd) {} };
注意:虚继承需要修改原有类的继承方式,如果这两个类已经在项目中广泛使用,要考虑兼容性问题。
方案3:组合替代多重继承
如果你对多重继承有所顾虑,也可以用组合的方式实现:让ReadWriteFd内部持有ReadFd和WriteFd的实例,然后通过转发函数对外暴露读写能力。
代码示例:
class ReadWriteFd { private: ReadFd read_fd_; WriteFd write_fd_; public: explicit ReadWriteFd(int fd) : read_fd_(fd), write_fd_(fd) {} // 转发读操作到read_fd_ ssize_t read(void* buf, size_t count) { return read_fd_.read(buf, count); } // 转发写操作到write_fd_ ssize_t write(const void* buf, size_t count) { return write_fd_.write(buf, count); } // 如果需要对外暴露文件描述符,需要给ReadFd/WriteFd加公共接口 int get_fd() const { // 因为两个实例的fd是同一个,取任意一个即可 // 这里假设ReadFd提供了get_fd()方法 return read_fd_.get_fd(); } };
这个方案的好处是避免了多重继承的复杂性,但缺点是需要手动写转发函数,如果ReadFd和WriteFd有很多成员函数,会比较繁琐。另外,原来的file_descriptor_是protected,所以需要给ReadFd和WriteFd添加get_fd()这样的公共访问接口,或者把ReadWriteFd声明为它们的友元。
内容的提问来源于stack exchange,提问作者TruLa

