前一段时间,我与一位同事讨论了如何在STL 映射中插入值。我更喜欢,
map[key] = value;
因为它感觉自然并且易于阅读,而他更喜欢
map.insert(std::make_pair(key, value))
我只是问他,我们俩都不记得插入效果更好的原因,但是我确信这不仅是样式偏好,还有技术上的原因,例如效率。在SGI STL参考只是说“严格地说,这个成员函数是不必要的:它的存在只是为了方便。”
谁能告诉我这个原因,还是我只是梦想有一个原因?
前一段时间,我与一位同事讨论了如何在STL 映射中插入值。我更喜欢,
map[key] = value;
因为它感觉自然并且易于阅读,而他更喜欢
map.insert(std::make_pair(key, value))
我只是问他,我们俩都不记得插入效果更好的原因,但是我确信这不仅是样式偏好,还有技术上的原因,例如效率。在SGI STL参考只是说“严格地说,这个成员函数是不必要的:它的存在只是为了方便。”
谁能告诉我这个原因,还是我只是梦想有一个原因?
Answers:
当你写
map[key] = value;
无法判断是否替换了value
for key
或是否使用创建了新key
的value
。
map::insert()
只会创建:
using std::cout; using std::endl;
typedef std::map<int, std::string> MyMap;
MyMap map;
// ...
std::pair<MyMap::iterator, bool> res = map.insert(MyMap::value_type(key,value));
if ( ! res.second ) {
cout << "key " << key << " already exists "
<< " with value " << (res.first)->second << endl;
} else {
cout << "created key " << key << " with value " << value << endl;
}
对于我的大多数应用程序,我通常不在乎是创建还是替换,因此我使用了易于阅读的map[key] = value
。
(res.first)->second
而不是value
第二种情况。
else
因为我认为使用value
方法比迭代器更清晰。仅当值的类型具有异常的副本ctor或op ==时,它才会有所不同,并且使用Map这样的STL容器,该类型将导致其他问题。
map.insert(std::make_pair(key,value))
应该是map.insert(MyMap::value_type(key,value))
。返回make_pair
的类型与所采用的类型不匹配,insert
并且当前解决方案需要转换
operator[]
,只需比较前后的大小即可。Imho只能调用map::operator[]
默认的可构造类型更为重要。
对于地图中已存在的键,两者具有不同的语义。因此它们并不是真正可直接比较的。
但是operator []版本需要默认构造值,然后进行赋值,因此,如果这要比复制构造昂贵,那么它将更加昂贵。有时默认构造没有意义,因此不可能使用operator []版本。
insert
从异常安全性的角度来看更好。
该表达式map[key] = value
实际上是两个操作:
map[key]
-使用默认值创建地图元素。= value
-将值复制到该元素中。第二步可能会发生异常。结果,该操作将仅部分完成(将新元素添加到map中,但该元素未使用初始化value
)。当操作未完成但系统状态被修改时的情况称为“有副作用”的操作。
insert
操作提供了有力的保证,这意味着它没有副作用(https://en.wikipedia.org/wiki/Exception_safety)。insert
是完全完成还是使地图保持未修改状态。
http://www.cplusplus.com/reference/map/map/insert/:
如果要插入单个元素,则在发生异常的情况下容器中不会有任何更改(强烈保证)。
如果您的应用程序对速度有严格要求,我会建议您使用[]运算符,因为它会创建原始对象的总共3个副本,其中2个是临时对象,并且早晚会被破坏为。
但是在insert()中,创建了原始对象的4个副本,其中3个是临时对象(不一定是“临时”)并被销毁了。
这意味着需要更多时间:1.一个对象内存分配2.一个额外的构造函数调用3.一个额外的析构函数调用4.一个对象内存释放
如果您的对象很大,则构造函数很典型,析构函数会释放大量资源,超过这些数量甚至更多。关于可读性,我认为两者都足够公平。
我想到了同样的问题,但不是可读性而是速度。这是一个示例代码,通过它我了解了我提到的要点。
class Sample
{
static int _noOfObjects;
int _objectNo;
public:
Sample() :
_objectNo( _noOfObjects++ )
{
std::cout<<"Inside default constructor of object "<<_objectNo<<std::endl;
}
Sample( const Sample& sample) :
_objectNo( _noOfObjects++ )
{
std::cout<<"Inside copy constructor of object "<<_objectNo<<std::endl;
}
~Sample()
{
std::cout<<"Destroying object "<<_objectNo<<std::endl;
}
};
int Sample::_noOfObjects = 0;
int main(int argc, char* argv[])
{
Sample sample;
std::map<int,Sample> map;
map.insert( std::make_pair<int,Sample>( 1, sample) );
//map[1] = sample;
return 0;
}
insert
必须进行相同的搜索,因此与之没有区别[]
(因为映射键是唯一的)。
请注意,您还可以使用Boost.Assign:
using namespace std;
using namespace boost::assign; // bring 'map_list_of()' into scope
void something()
{
map<int,int> my_map = map_list_of(1,2)(2,3)(3,4)(4,5)(5,6);
}
这是另一个示例,显示了如果存在键,则operator[]
覆盖键的值,如果存在,.insert
则不覆盖键的值。
void mapTest()
{
map<int,float> m;
for( int i = 0 ; i <= 2 ; i++ )
{
pair<map<int,float>::iterator,bool> result = m.insert( make_pair( 5, (float)i ) ) ;
if( result.second )
printf( "%d=>value %f successfully inserted as brand new value\n", result.first->first, result.first->second ) ;
else
printf( "! The map already contained %d=>value %f, nothing changed\n", result.first->first, result.first->second ) ;
}
puts( "All map values:" ) ;
for( map<int,float>::iterator iter = m.begin() ; iter !=m.end() ; ++iter )
printf( "%d=>%f\n", iter->first, iter->second ) ;
/// now watch this..
m[5]=900.f ; //using operator[] OVERWRITES map values
puts( "All map values:" ) ;
for( map<int,float>::iterator iter = m.begin() ; iter !=m.end() ; ++iter )
printf( "%d=>%f\n", iter->first, iter->second ) ;
}
这是一个相当有限的案例,但是从我收到的评论来看,我认为这是值得注意的。
我以前看过人们以以下形式使用地图
map< const key, const val> Map;
避免意外值覆盖的情况,但接着继续编写其他一些代码:
const_cast< T >Map[]=val;
我记得他们这样做的原因是因为他们确定在这些特定的代码位中它们不会覆盖映射值。因此,继续采用更具“可读性”的方法[]
。
我从未真正从这些人编写的代码中遇到任何直接的麻烦,但是直到今天,我坚信直到可以轻易避免的风险(无论风险多么小)都不应被承担。
如果要处理绝对不能覆盖的映射值,请使用insert
。不要仅仅为了可读性而设置例外。
insert
(not input
),因为const_cast
将会导致任何先前的值被覆盖,这是非常不固定的。或者,不要将值类型标记为const
。(这类事情通常是的最终结果const_cast
,因此几乎总是一个红旗表示其他地方存在错误。)
insert
您在要防止值被覆盖的情况下使用。(只需将其更改input
为insert
-谢谢)
const_cast<T>(map[key])
是:1. []更具可读性; 2.他们对某些代码有信心,他们不会覆盖值;以及3.他们没有希望其他未知代码覆盖它们的值-因此const value
。
const_cast
似乎不能消除的额外“可读性” []
,而这种信心几乎足以解雇开发人员。棘手的运行时条件是通过防弹设计而不是直觉来解决的。
std :: map insert()
函数不会覆盖与键关联的值的事实使我们可以编写如下的对象枚举代码:
string word;
map<string, size_t> dict;
while(getline(cin, word)) {
dict.insert(make_pair(word, dict.size()));
}
当我们需要将不同的非唯一对象映射到范围为0..N的某些id时,这是一个非常常见的问题。这些ID可以稍后在例如图算法中使用。operator[]
在我看来,替代似乎不太可读:
string word;
map<string, size_t> dict;
while(getline(cin, word)) {
size_t sz = dict.size();
if (!dict.count(word))
dict[word] = sz;
}