如何在 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 中隐藏 iteratorpair 的概念。 为了满足这个新要求,第一个想法是在内部存储一个迭代器 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 是的,奇怪的是不可能eraseconst_iterator。我知道这个约束是有争议的(访问不可变对象与从容器中删除不可变对象不同) map::erase 已在 C++17 中修复,以接受 iteratorconst_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,这是一个带有boolchar[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?

如何在 C# 中配置 XML 解析器以禁用外部实体解析

如何在 Weka 中构建 SVM 分类器以仅考虑数据集中的某些特征?

Java Iterables 使用每个 Foreach 构造“重置”迭代器