如何实现兼容虚调用、可自动组装继承类的类变体模板Multi
可赋值、支持虚分派且内存连续的Multi模板实现方案
需求说明
现有如下C++继承结构:
struct Parent { virtual int get() = 0; }; struct A : public Parent { int get() { return 1; } }; struct B : public Parent { int get() { return 2; } }; struct C : public Parent { int get() { return 3; } };
需要实现一个Multi模板,满足以下要求:
- 实例化
Multi<Parent, A, B, C>后,可直接接受A、B、C的实例赋值(行为类似std::variant) - 能直接调用
get()方法,通过虚分派执行对应子类的实现(行为类似Parent*) - 不使用堆指针,所有内存存储在对象内部,保证内存连续
可行性分析
该需求完全可行。核心思路是在Multi内部开辟一块足够容纳最大子类实例的对齐缓冲区,直接在缓冲区中构造子类对象,同时维护一个指向缓冲区的Parent*指针用于虚调用。这种方式既避免了外部指针,保证内存连续,又能利用C++的多态机制实现虚分派,同时通过重载赋值运算符实现类似std::variant的赋值行为。
具体实现代码
#include <type_traits> #include <utility> #include <algorithm> #include <cstring> template <typename Base, typename... Deriveds> class Multi { private: // 计算所有子类中最大的大小和对齐要求 static constexpr size_t max_size = std::max({sizeof(Deriveds)...}); static constexpr size_t max_align = std::max({alignof(Deriveds)...}); // 用于存储子类实例的对齐缓冲区 std::aligned_storage_t<max_size, max_align> buffer; // 指向缓冲区中对象的基类指针,用于虚分派 Base* ptr = nullptr; // 销毁当前存储的对象 void destroy_current() { if (ptr != nullptr) { std::destroy_at(ptr); ptr = nullptr; } } public: // 默认构造函数 Multi() = default; // 接受子类实例的构造函数 template <typename Derived> Multi(Derived&& obj) { static_assert(std::is_base_of_v<Base, std::decay_t<Derived>>, "Type must derive from Base"); ptr = std::construct_at(reinterpret_cast<std::decay_t<Derived>*>(&buffer), std::forward<Derived>(obj)); } // 析构函数:销毁当前对象 ~Multi() { destroy_current(); } // 赋值运算符:支持子类实例赋值 template <typename Derived> Multi& operator=(Derived&& obj) { static_assert(std::is_base_of_v<Base, std::decay_t<Derived>>, "Type must derive from Base"); destroy_current(); ptr = std::construct_at(reinterpret_cast<std::decay_t<Derived>*>(&buffer), std::forward<Derived>(obj)); return *this; } // 禁用拷贝构造和拷贝赋值(如需支持可自行扩展) Multi(const Multi&) = delete; Multi& operator=(const Multi&) = delete; // 移动构造和移动赋值 Multi(Multi&& other) noexcept : ptr(other.ptr) { other.ptr = nullptr; // 缓冲区内容直接移动(因为是POD类型,memcpy即可) std::memcpy(&buffer, &other.buffer, max_size); } Multi& operator=(Multi&& other) noexcept { if (this != &other) { destroy_current(); ptr = other.ptr; other.ptr = nullptr; std::memcpy(&buffer, &other.buffer, max_size); } return *this; } // 调用虚函数get() int get() const { return ptr->get(); } }; // 测试代码 #include <iostream> int main() { Multi<Parent, A, B, C> m; m = A{}; std::cout << m.get() << std::endl; // 输出1 m = B{}; std::cout << m.get() << std::endl; // 输出2 m = C{}; std::cout << m.get() << std::endl; // 输出3 Multi<Parent, A, B, C> m2 = std::move(m); std::cout << m2.get() << std::endl; // 输出3 return 0; }
关键实现说明
- 缓冲区设计:使用
std::aligned_storage_t创建一块大小为最大子类尺寸、对齐符合最严格子类要求的缓冲区,确保所有子类都能在其中正确构造。 - 虚分派支持:通过
Base* ptr指向缓冲区中的子类对象,调用get()时会自动触发虚函数机制,执行对应子类的实现。 - 赋值与构造:通过模板化的构造函数和赋值运算符,接受任意子类的实例,先销毁当前缓冲区中的对象,再构造新的子类实例,保证状态正确。
- 内存连续性:缓冲区直接作为
Multi类的成员变量,所有内存都包含在Multi对象内部,无需额外堆分配,保证内存连续。
内容的提问来源于stack exchange,提问作者ZeroZ30o
相关产品推荐
相关产品推荐

