如何在 API 中隐藏迭代器以使用 std::map 包装器中的项目
Posted
技术标签:
【中文标题】如何在 API 中隐藏迭代器以使用 std::map 包装器中的项目【英文标题】:How to hide iterators in the API to consume an item in a std::map wrapper 【发布时间】:2021-07-17 12:53:27 【问题描述】:在一个 c++98 项目中,我有一个类 Items
包装了一个 std::map
:
#include <string>
#include <map>
class Item /* ... */ ; // Some class holding some data
class Items
public:
typedef std::map<std::string,Item> container_type;
typedef container_type::iterator iterator;
Items()
iterator find(const String& k) return i_map.find(k);
iterator end() return i_map.end();
void erase(iterator i) i_map.erase(i);
// ...
private:
container_type i_map;
;
它的主要用途是搜索某个Item
,如果找到则使用并删除它。
我为consume 提出的第一个明显的 API 是这样的:
Items items;
Items::iterator i = items.find("some-id");
if( i!=items.end() )
const Item& item = i->second; // Get and use the item
items.erase(i); // Item consumed: remove it from items
...但是我被要求在类 API 中隐藏 iterator
和 pair
的概念。
为了满足这个新要求,第一个想法是在内部存储一个迭代器 i_found
以记住最后找到的项目:
#include <stdexcept>
#include <string>
#include <map>
class Item /* ... */ ; // Some class holding some data
class Items
public:
typedef std::map<std::string,Item> container_type;
typedef container_type::iterator iterator;
Items() : i_found( i_map.end() )
#define should_be_const // const // Unfortunately 'i_found' must be a non const 'iterator' in order to be erased
bool contains(const std::string& k) should_be_const
i_found = i_map.find(k);
return i_found!=i_map.end();
const Item& get(const std::string& k) should_be_const
if(i_found==i_map.end() || k!=i_found->first) i_found = i_map.find(k); // search if necessary
if(i_found!=i_map.end()) return i_found->second;
else throw std::runtime_error("key \'" + std::string(k) + "\' not found!");
void erase_found()
i_map.erase(i_found);
i_found = i_map.end(); // invalidate last find
private:
container_type i_map;
mutable iterator i_found; // Last found item
;
这样就可以写了:
Items items;
if( items.contains("some-id") )
const Item& item = items.get("some-id"); // Get and use the item
items.erase_found(); // Item used: remove it from items
我知道这是否是一种改进值得商榷,我不是在问这个(是的,我也不喜欢它)。
在最后一个实现中,有没有办法让方法 contains()
和 get()
const
?
鉴于上述要求,我也对有关不同方法的建议感兴趣。
虽然Item
复制构造是可以接受的,但如果找不到“some-id”,我想避免构造item
,因为在这个替代方案中我拼命尝试:
bool Items::extract_if_present(const std::string& k, Item& item)
iterator i = i_map.find(k);
if( i != i_map.end() )
item = i->second;
i_map.erase(i);
return true;
return false;
Item item; // <-- Avoidable useless work if not found
if( items.extract_if_present("some-id", item) )
//item; // Use the copied item
【问题讨论】:
const_cast
i_map
能够使用const
?
@Jarod42 他们告诉我,const
方法中的 const_cast
是一个不兑现的承诺,但是是的,在这种情况下,这只是一个 易碎 承诺
i_map.find
本身不会发生变异,因此该部分不会直接违反承诺(更多的是 mutable
字段)。
@Jarod42 是的,奇怪的是不可能erase
和const_iterator
。我知道这个约束是有争议的(访问不可变对象与从容器中删除不可变对象不同)
map::erase
已在 C++17 中修复,以接受 iterator
和 const_iterator
。
【参考方案1】:
使用optional
(不是std,它是C++17,但可以在C++98中完成(例如使用boost
)),那么你可能在接口中
optional<Item> get(const std::string&) const;
optional<Item> extract(const std::string&);
用法类似于:
Items items;
// ...
if (optional<Item> item = items.extract("some-id"))
// use *item;
【讨论】:
我很高兴看到确认了我对std::optional
的需求。不幸的是,此时我被一个旧的不受支持的编译器(Borland C++ 5.5.1)困住了,所以我必须选择一个旧版本的 boost,只要我获得在项目中包含那个大库的授权。我将再次搜索替代实现。
我很想为get
使用原始指针。您可以相当轻松地编写自己的optional
,这是一个带有bool
和char[sizeof(Item)]
的类【参考方案2】:
因为找不到key就是在扔,所以可以有比map
更简单的界面。
class Items
public:
// do these need to be public?
typedef std::map<std::string, Item> container_type;
typedef container_type::iterator iterator;
bool contains(const std::string& k) const
return i_map.find(k)!=i_map.end();
const Item& get(const std::string& k) const
iterator i_found = i_map.find(k);
if(i_found!=i_map.end()) return i_found->second;
else throw std::runtime_error("key \'" + k + "\' not found!");
Item extract(const std::string& k)
iterator i_found = i_map.find(k);
if(i_found!=i_map.end())
Item temp = i_found->second;
map.erase(i_found);
return temp;
else throw std::runtime_error("key \'" + k + "\' not found!");
private:
container_type i_map;
;
如果你迁移到 C++11,extract 可以移动而不是复制。
【讨论】:
在extract
之前调用contains
意味着调用find
两次,对吗?除非在trycatch
中使用extract
,但这会涉及正常执行流程中的异常,不可取。顺便说一句,有一天我也可以使用auto
,在将我的项目移植到更新的开发环境之后;-)以上是关于如何在 API 中隐藏迭代器以使用 std::map 包装器中的项目的主要内容,如果未能解决你的问题,请参考以下文章
是否有通过 Windows 资源管理器以编程方式剪切/复制/粘贴文件的 Windows API?