C++ 是否有任何理由不使用异常来测试std::map中是否存在元素

C++ 是否有任何理由不使用异常来测试std::map中是否存在元素,c++,exception,c++11,map,stl,C++,Exception,C++11,Map,Stl,我最近开始在许多项目中使用c++11,并且也开始大量使用stl容器,我对stl容器比较陌生 我最近写了一个函数,它做了类似的事情: CMyClass* CreateAndOrGetClass( int _iObjectId, std::map<int, CMyClass*>& _mapObjectData ) { CMyClass* pClassInstance{ nullptr }; try { pClassInstance = _

我最近开始在许多项目中使用c++11,并且也开始大量使用stl容器,我对stl容器比较陌生

我最近写了一个函数,它做了类似的事情:

CMyClass* CreateAndOrGetClass( int _iObjectId, std::map<int, CMyClass*>& _mapObjectData )
{
    CMyClass* pClassInstance{ nullptr };

    try
    {
        pClassInstance = _mapObjectData.at( _iObjectId);
    }
    catch ( ... )
    {
        pClassInstance = new CMyClass();
        __mapObjectData.insert( _iObjectId, pClassInstance );
    }

    return ( pClassInstance );
}
CMyClass*createAndGetClass(int\u iObjectId,std::map和\u mapObjectData)
{
CMyClass*pClassInstance{nullptr};
尝试
{
pClassInstance=\u mapObjectData.at(\u IOObjectId);
}
捕获(…)
{
pClassInstance=new CMyClass();
__插入(_iObjectId,pClassInstance);
}
返回(pClassInstance);
}
我的问题是关于在显然不是“例外”的情况下使用例外。这似乎是一种实现当前目标的非常简洁的方法,而不涉及设置迭代器

对于使用异常实现这种类型的目的,是否存在我可能遗漏的任何问题


测量值*

因此,为了跟进,我做了一些性能测试,将基于异常的代码与所选答案中的代码示例进行比较。这是一个很好的练习,可以在正常的代码流中使用异常来检查并限定性能差的建议。它还提供了一些很好的参考资料,用于在不抛出异常的情况下断言接近零的性能损失

虽然这些测试并不详尽,但我观察到:

每个测试都使用rand()作为映射键的源(最多生成32768个元素),在大量插入/获取上运行:

异常方法:平均5.99秒
查找/添加方法:平均0.75秒

将随机元素的范围增加十倍,则返回以下数字:

异常方法:平均56.7秒
查找/添加方法:平均4.54秒

然后,我用所有可能的键条目预先填充映射,以便try不会抛出:

异常方法:平均0.162秒
查找/添加方法:平均0.158秒


<> P.>奇怪的是,对于VStudio,调试模式代码使用异常处理方法更快。

< P>有一个理由在C++中避免使用太多的异常:异常处理很重(因为所有中间调用帧中的局部变量的所有析构函数都必须执行)。 在Ocaml中,异常处理是轻量级的,所以它们更有可能按照您的建议使用


在您的示例中,我宁愿使用
std::map

编译器的成员函数,编译器通常使用一种策略来实现运行时开销为零的异常,只要不抛出任何异常,但是如果抛出异常,然后,由于异常处理机制必须解除堆栈,它会影响程序的性能

即使这对于您的用例是可以接受的,在您的用例中使用异常来管理控制流也没有任何好处。使用
map::find
不仅更加简洁,而且更加惯用

auto iter = _mapObjectData.find(_iObjectId);
if(iter == _mapObjectData.end()) {
  auto instance = new CMyClass();
  _mapObjectData.insert(std::make_pair(_iObjectId, instance));
  return instance;
}
return iter->second;

@Mehrdad在注释中有一个用于定位键的注释,而不是
map::find
。这样做的好处是,如果键不存在,则返回值可以用作
map::insert
的提示,这将提高插入性能

auto iter = _mapObjectData.lower_bound(_iObjectId);
if(iter == _mapObjectData.end() || iter->first != _iObjectId) {
  auto instance = new CMyClass();
  _mapObjectData.insert(iter, std::make_pair(_iObjectId, instance));
  return instance;
}
return iter->second;

我还强烈建议将地图的类型从

std::map<int, CMyClass*>
std::map

std::map

将拥有资源的原始指针粘贴到标准库容器中通常会带来更多麻烦。

是的,因为传播异常和代码本身都不是特别有效的

忽略一个事实,即首先不应该存储原始指针,接下来最正确、最有效的方法是使用
运算符[]

CMyClass* CreateAndOrGetClass(int _iObjectId, std::map<int, CMyClass*>& _mapObjectData)
{
    CMyClass* &pClassInstance = _mapObjectData[_iObjectId];
    if (!pClassInstance) { pClassInstance = new CMyClass(); }
    return pClassInstance;
}
CMyClass*createAndGetClass(int\u iObjectId,std::map和\u mapObjectData)
{
CMyClass*&pClassInstance=\u mapObjectData[\u IOObjectId];
如果(!pClassInstance){pClassInstance=new CMyClass();}
返回pClassInstance;
}
这样,映射中的查找只执行一次,并且该值被构建到位


当然,实际上,您可能应该只存储一个
CMyClass
作为值,而不是
CMyClass*
,但这与我在这里的回答无关。

异常是一种将故障处理与正常情况代码清晰地分开的方法

在您的代码中,它们用于正常情况,这违背了目的,也没有任何优势

在手头的特定情况下,使用
[]
索引,如果键不存在,它会自动插入键。并且更普遍地使用条件构造来实现简单的条件控制流。在一些异常情况下,异常对于表示正常的案例控制流(例如,从深度嵌套的递归调用返回结果)是有意义的,因为代码可以变得更简单、更清晰,但这些异常情况是……异常的


关于效率,抛出异常是昂贵的,因为C++编译器被优化只用于异常处理,这是罕见的。 当失败成为常态时,重新考虑失败的工作定义

然而,仅仅是抛出异常的可能性就几乎没有开销(最低为0)。因此,您不应该害怕使用异常来安全地、不分散注意力地从正常案例代码中获得故障处理。如果使用得当,将代码分为正常情况和故障,异常是双赢的


在回答另一个问题时,你说


尽管我喜欢它的简洁性,但如果不能实例化该类,它可能会在映射中留下一个null ptr

这是一种失败的情况,使用e
CMyClass* CreateAndOrGetClass(int _iObjectId, std::map<int, CMyClass*>& _mapObjectData)
{
    CMyClass* &pClassInstance = _mapObjectData[_iObjectId];
    if (!pClassInstance) { pClassInstance = new CMyClass(); }
    return pClassInstance;
}
Your_class* creative_at( int const id, std::map<int, YourClass*>& object_data )
{
    // Basic non-creating at:
    {
        auto const it = object_data.find( id );
        if( it != object_data.end() ) { return it->second; }
    }

    // Create:
    std::unique_ptr<Your_class> p( new Your_class() );    // May throw.
    object_data[id] = p.get();                            // May throw.
    return p.release();
}
if(!mymap.count(objectId))
{

}