如何可靠继承无非静态数据成员的C++类?
问题场景
我有大量类继承自如下这类基类:它仅提供实用函数、声明虚函数,但没有任何非静态数据成员。示例代码如下:
class Printable { public: virtual ~Printable() noexcept = default; virtual void print(std::ostream& out) const = 0; std::string toString() const { std::ostringstream oss; this->print(oss); return oss.str(); } }; std::ostream& operator<<(std::ostream& out, Printable const& p) { p.print(out); return out; }
当普通类继承这类基类以复用功能时,其实不需要处理父类的归档、swap操作,但这一点无法直观确认,必须了解父类层级或者查阅文档。
核心疑问1
有没有一种更可靠的编译期或运行期方法,能确认父类不存在非静态数据成员?
举个例子,子类实现时总会纠结要不要处理父类部分:
class Foo : public Printable { private: Some members; public: // Constructors... virtual void print(std::ostream& out) const { out << Some::printer(this->members); } void swap(Foo& foo) noexcept { // 纠结:要不要处理Printable里的内容? this->members.swap(foo.members); } void save(Archive& ar) const { // 纠结:要不要保存Printable里的内容? this->members.save(ar); } };
我认为std::is_empty在这里不适用,因为基类包含虚函数(带虚函数的类会有虚表指针,std::is_empty会返回false)。
尝试过的方案及痛点
我试过在Printable中仅定义空构造函数并禁用拷贝,这样能明确父类无数据需要管理,但会失去默认构造函数的便捷性:
class Printable { public: Printable() noexcept = default; Printable(Printable const&) = delete; Printable& operator=(Printable const&) = delete; virtual ~Printable() noexcept = default; virtual void print(std::ostream& out) const = 0; }; class Foo : public Printable { private: Some members; public: Foo() = default; Foo(Foo const&) = default; // 错误:父类没有拷贝构造函数 // 不得不手动写: Foo(Foo const& foo) : Printable(), members(foo.members) {} // 如果成员很多的话,这会非常繁琐... };
核心疑问2
还有其他可行方案吗?
可行解决方案
1. 编译期断言:自定义类型 trait 检查内存布局
虽然std::is_empty对带虚函数的类不生效,但我们可以通过对比仅继承基类的空派生类与基类的大小,结合编译期断言来验证:
#include <type_traits> template <typename Base> struct has_no_non_static_data_members { template <typename T> static constexpr bool test(T*) { struct Derived : Base {}; // 多态基类的大小由虚表指针决定,若派生类大小等于基类,说明基类无非静态数据 return std::is_polymorphic_v<Base> && sizeof(Derived) == sizeof(Base); } static constexpr bool value = test(static_cast<Base*>(nullptr)); }; // 子类中添加编译期断言,编译时验证基类无额外数据 class Foo : public Printable { static_assert(has_no_non_static_data_members<Printable>::value, "Printable must have no non-static data members"); // ... 其他成员实现 };
如果Printable后续新增非静态数据成员,sizeof(Derived)会大于sizeof(Base),断言会直接触发编译错误。
2. 基类显式标记:直观告知子类特性
在基类中添加静态常量或注释,明确标记自身无任何非静态数据成员,子类可直接读取该标记或做编译期检查:
class Printable { public: // 显式标记:此类无任何非静态数据成员 static constexpr bool has_no_non_static_data = true; virtual ~Printable() noexcept = default; virtual void print(std::ostream& out) const = 0; std::string toString() const { /* ... */ } }; // 子类中直接验证标记 class Foo : public Printable { static_assert(Printable::has_no_non_static_data, "Base class contains non-static data members"); // ... 其他成员实现 };
这种方式简单直接,只要维护基类时保持标记的准确性,就能让子类快速确认特性。
3. 基类提供空实现的通用方法
直接在基类中提供swap、save、load等方法的空实现,子类无需纠结是否需要处理父类,直接调用即可:
class Printable { public: virtual ~Printable() noexcept = default; virtual void print(std::ostream& out) const = 0; std::string toString() const { /* ... */ } // 空实现,子类可放心调用 void swap(Printable&) noexcept {} void save(Archive&) const {} void load(Archive&) {} }; // 子类中直接调用基类方法,无需纠结 class Foo : public Printable { void swap(Foo& foo) noexcept { Printable::swap(foo); // 即使基类无数据也安全 this->members.swap(foo.members); } };
这种方式从根源上消除了子类的疑问,即使基类未来新增数据,子类代码也能兼容。
内容的提问来源于stack exchange,提问作者Caduchon
相关产品推荐
相关产品推荐

