多类型C++容器中对象统一接口访问:寻求更高效实现方案
统一访问不同容器中关联对象的高效方案
问题背景
我有一组相互关联的对象,分别存储在std::vector、std::deque、std::list等不同类型的容器中,希望用独立于具体容器的统一接口访问它们。目前有两种实现方式,但都存在缺陷:
- 普通指针访问:迭代器是容器特定的,只能用指针,但
std::vector扩容时会导致指针失效 - 双索引访问:用容器ID+元素索引定位,通过
GetData()函数获取值,但访问std::list时需要调用std::next,时间复杂度O(n),效率低
请问有没有运行时效率更高的实现方式?
现有代码示例
#include <cstdint> #include <deque> #include <iostream> #include <iterator> #include <list> #include <vector> struct C { int data = 0; // 方案1:指针访问 const C *pOther = 0; // 方案2:双索引访问 size_t idOther = 0; uint8_t idContainer = 0; C(int i) : data(i) {} }; std::vector<C> vc; std::deque<C> dc; std::list<C> lc; // 方案2的获取函数 int GetData(size_t id, uint8_t idc) { switch(idc) { case 1: return vc[id].data; case 2: return dc[id].data; case 3: return std::next(lc.begin(), id)->data; } return -1; } int main() { C c1(1), c2(2), c3(3); vc.push_back(c1); dc.push_back(c2); lc.push_back(c3); // 测试方案1 vc[0].pOther = &dc[0]; dc[0].pOther = &lc.front(); lc.front().pOther = &vc[0]; std::cout << "\nvc[0].data = " << vc[0].data << " pOther->data = " << vc[0].pOther->data << "\ndc[0].data = " << dc[0].data << " pOther->data = " << dc[0].pOther->data << "\nlc.front().data = " << lc.front().data << " pOther->data = " << lc.front().pOther->data << std::endl; // 测试方案2 vc[0].idOther = 0; vc[0].idContainer = 2; dc[0].idOther = 0; dc[0].idContainer = 3; lc.front().idOther = 0; lc.front().idContainer = 1; std::cout << "\nvc[0].data = " << vc[0].data << " GetData = " << GetData(vc[0].idOther, vc[0].idContainer) << "\ndc[0].data = " << dc[0].data << " GetData = " << GetData(dc[0].idOther, dc[0].idContainer) << "\nlc.front().data = " << lc.front().data << " GetData = " << GetData(lc.front().idOther, lc.front().idContainer) << std::endl; return 0; }
更优实现方案
方案1:稳定句柄+std::function类型擦除
利用类型擦除封装不同容器的稳定访问方式,同时提供统一接口,兼顾稳定性和效率:
核心思路
- 针对
std::vector:存储容器引用+元素索引(扩容后索引仍然有效) - 针对
std::deque/std::list:直接存储元素指针(这两个容器的元素不会因扩容/首尾操作移动,指针稳定) - 用
std::function<const C&()>封装上述访问逻辑,提供统一的调用接口
实现代码
#include <cstdint> #include <deque> #include <iostream> #include <iterator> #include <list> #include <vector> #include <functional> struct C { int data = 0; // 统一稳定句柄 std::function<const C&()> get_other; C(int i) : data(i) {} }; std::vector<C> vc; std::deque<C> dc; std::list<C> lc; // 生成不同容器的访问句柄 std::function<const C&()> make_handle(std::vector<C>& container, size_t idx) { return [&container, idx]() -> const C& { return container[idx]; }; } std::function<const C&()> make_handle(std::deque<C>& container, size_t idx) { return [ptr = &container[idx]]() -> const C& { return *ptr; }; } std::function<const C&()> make_handle(std::list<C>& container, size_t idx) { auto it = std::next(container.begin(), idx); return [ptr = &*it]() -> const C& { return *ptr; }; } // 直接用list迭代器生成句柄,避免std::next的开销(推荐) std::function<const C&()> make_handle(std::list<C>& container, std::list<C>::iterator it) { return [ptr = &*it]() -> const C& { return *ptr; }; } int main() { // 直接构造对象到容器中,避免拷贝 vc.emplace_back(1); dc.emplace_back(2); lc.emplace_back(3); // 设置关联关系 vc[0].get_other = make_handle(dc, 0); dc[0].get_other = make_handle(lc, lc.begin()); lc.front().get_other = make_handle(vc, 0); // 统一接口访问 std::cout << "\nvc[0].data = " << vc[0].data << " other->data = " << vc[0].get_other().data << "\ndc[0].data = " << dc[0].data << " other->data = " << dc[0].get_other().data << "\nlc.front().data = " << lc.front().data << " other->data = " << lc.front().get_other().data << std::endl; // 测试vector扩容,验证句柄有效性 vc.reserve(100); // 手动扩容模拟场景 vc.emplace_back(4); std::cout << "\nVector扩容后,vc[0].other->data = " << vc[0].get_other().data << std::endl; return 0; }
优势
- 稳定性:vector扩容后句柄依然有效,deque/list的指针不会因常规操作失效
- 效率:
std::function调用开销极小,访问时间复杂度O(1),避免了双索引方案中list的O(n)开销 - 统一接口:所有关联对象都通过
get_other()访问,无需关心底层容器类型
方案2:自定义轻量类型擦除(比std::function更高效)
如果追求极致性能,可以自定义类型擦除类,去掉std::function的额外开销:
struct StableHandle { // 虚基类定义接口 struct Concept { virtual ~Concept() = default; virtual const C& get() const = 0; }; // 模板实现类,封装具体访问逻辑 template<typename Func> struct Model : Concept { Func func; Model(Func f) : func(std::move(f)) {} const C& get() const override { return func(); } }; std::unique_ptr<Concept> impl; // 构造函数,接受任意返回C&的可调用对象 template<typename Func> StableHandle(Func f) : impl(std::make_unique<Model<Func>>(std::move(f))) {} // 统一调用接口 const C& operator()() const { return impl->get(); } };
将struct C中的get_other替换为StableHandle get_other;,用法和std::function版本完全一致。这个自定义类的开销比std::function更小,适合对性能要求极高的场景。
方案对比总结
| 方案 | 稳定性 | 访问效率 | 接口统一性 |
|---|---|---|---|
| 普通指针 | Vector扩容时失效 | O(1) | 统一 |
| 双索引 | 完全稳定 | List为O(n),其余为O(1) | 需区分容器 |
| std::function句柄 | 完全稳定 | O(1)(极小调用开销) | 统一 |
| 自定义类型擦除句柄 | 完全稳定 | O(1)(接近直接访问) | 统一 |
内容的提问来源于stack exchange,提问作者Pietro
相关产品推荐
相关产品推荐

