寻求std::copy风格的向量值映射函数,适配多种标准库容器
嘿,我太懂这种感觉了——明明觉得C++标准库应该有这种能通吃unordered_set、unordered_map、deque这类容器的通用工具,翻遍文档却找不到,还不想写一堆模板特化来适配每个容器,对吧?
首先得说清楚:C++标准库确实没有内置这种跨所有容器类型的通用操作,毕竟不同容器的接口差异不小——无序关联容器是键值/单元素集合,deque是序列容器,它们的核心操作(比如查找、插入)的调用方式都不一样。但要避免让你直接写模板适配的话,类型擦除是个非常实用的思路。
1. 用抽象接口封装容器操作
我们可以先把你需要的核心操作(比如“检查元素是否存在”、“添加元素”)抽象成一个通用接口,然后底层用模板适配不同容器,但对外完全看不到模板。
比如先定义一个抽象基类:
#include <any> #include <memory> #include <algorithm> #include <unordered_set> #include <unordered_map> #include <deque> class ContainerHandler { public: virtual ~ContainerHandler() = default; // 示例操作1:检查元素是否存在 virtual bool has_element(const std::any& elem) const = 0; // 示例操作2:添加元素 virtual void add_element(const std::any& elem) = 0; };
然后为每个容器写一个实现类,这里用模板来复用代码,但用户不用直接接触:
template <typename Container> class ContainerImpl : public ContainerHandler { private: Container& m_container; public: explicit ContainerImpl(Container& container) : m_container(container) {} bool has_element(const std::any& elem) const override { using ValueType = typename Container::value_type; using KeyType = typename Container::key_type; // 针对不同容器类型做适配 if constexpr (std::is_same_v<Container, std::deque<ValueType>>) { return std::find(m_container.begin(), m_container.end(), std::any_cast<const ValueType&>(elem)) != m_container.end(); } else if constexpr (std::is_same_v<Container, std::unordered_set<ValueType>>) { return m_container.count(std::any_cast<const ValueType&>(elem)) > 0; } else if constexpr (std::is_same_v<Container, std::unordered_map<KeyType, typename Container::mapped_type>>) { // 这里假设传入的elem是键值,如果你需要检查键值对可以调整 return m_container.count(std::any_cast<const KeyType&>(elem)) > 0; } // 可以扩展更多容器类型,比如vector、set等 return false; } void add_element(const std::any& elem) override { using ValueType = typename Container::value_type; using KeyType = typename Container::key_type; if constexpr (std::is_same_v<Container, std::deque<ValueType>>) { m_container.push_back(std::any_cast<const ValueType&>(elem)); } else if constexpr (std::is_same_v<Container, std::unordered_set<ValueType>>) { m_container.insert(std::any_cast<const ValueType&>(elem)); } else if constexpr (std::is_same_v<Container, std::unordered_map<KeyType, typename Container::mapped_type>>) { // 这里假设elem是pair<key, value> auto [key, val] = std::any_cast<const std::pair<KeyType, typename Container::mapped_type>&>(elem); m_container.emplace(key, val); } } };
最后写一个工厂函数,自动推导容器类型,对外完全无模板:
std::unique_ptr<ContainerHandler> make_container_handler(auto& container) { return std::make_unique<ContainerImpl<decltype(container)>>(container); }
使用示例
这时候你用起来就非常清爽,完全不用管模板:
#include <iostream> int main() { std::unordered_set<int> my_set = {1, 3, 5}; auto set_handler = make_container_handler(my_set); std::cout << "Set has 3? " << std::boolalpha << set_handler->has_element(3) << "\n"; // 输出true set_handler->add_element(7); std::deque<std::string> my_deque = {"apple", "banana"}; auto deque_handler = make_container_handler(my_deque); deque_handler->add_element("cherry"); std::cout << "Deque has cherry? " << deque_handler->has_element("cherry") << "\n"; // 输出true std::unordered_map<int, std::string> my_map = {{1, "one"}, {2, "two"}}; auto map_handler = make_container_handler(my_map); std::cout << "Map has key 2? " << map_handler->has_element(2) << "\n"; // 输出true map_handler->add_element(std::make_pair(3, "three")); }
2. 有限容器类型?用std::variant更轻量
如果你只需要适配固定的几种容器,std::variant也是个不错的选择,不用抽象基类,用std::visit来分发操作:
#include <variant> // 定义支持的容器类型(注意用引用,避免拷贝) using SupportedContainers = std::variant< std::unordered_set<int>&, std::unordered_map<int, std::string>&, std::deque<std::string>& >; bool contains(const SupportedContainers& container, const std::any& elem) { return std::visit([&elem](auto& c) -> bool { using ContainerType = std::decay_t<decltype(c)>; if constexpr (std::is_same_v<ContainerType, std::unordered_set<int>>) { return c.count(std::any_cast<int>(elem)) > 0; } else if constexpr (std::is_same_v<ContainerType, std::unordered_map<int, std::string>>) { return c.count(std::any_cast<int>(elem)) > 0; } else if constexpr (std::is_same_v<ContainerType, std::deque<std::string>>) { return std::find(c.begin(), c.end(), std::any_cast<const std::string&>(elem)) != c.end(); } return false; }, container); }
使用示例
int main() { std::unordered_map<int, std::string> my_map = {{1, "one"}}; SupportedContainers var = my_map; std::cout << "Map has key 1? " << std::boolalpha << contains(var, 1) << "\n"; }
关于“避免模板”的说明
其实底层还是用到了模板,但这些模板都被封装起来了——你作为用户完全看不到模板代码,只需要调用无模板的接口。如果完全不用模板,那只能为每个容器写重复的操作代码,这显然不现实,所以类型擦除是平衡灵活性和易用性的最佳方案。
另外,std::any会带来一点点性能开销,如果你的场景对性能要求极高,可以考虑用更定制化的类型擦除方式(比如自定义variant代替std::any),但大多数日常场景下std::any足够好用了。
内容的提问来源于stack exchange,提问作者Chris

