{"id":101072,"date":"2019-02-27T07:00:00","date_gmt":"2019-02-27T22:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=101072"},"modified":"2019-03-12T23:57:55","modified_gmt":"2019-03-13T06:57:55","slug":"20190227-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20190227-00\/?p=101072","title":{"rendered":"The std::map subscript operator is a convenience, but a potentially dangerous one"},"content":{"rendered":"<p>The <code>std::map&lt;K, V&gt;<\/code> class is an associative container of key\/value pairs. It also provides a handy subscript operator <code>[]<\/code> that lets you access the map as if it were an array. <\/p>\n<p>That operator is a convenience, but a potentially dangerous one. <\/p>\n<p>The problem is that the <code>[]<\/code> operator is trying to serve two masters. You might use it to read a value from the map: <\/p>\n<pre>\nstd::map&lt;int, T&gt; m;\n\nauto value = m[2]; \/\/ retrieve the item whose key is 2\n<\/pre>\n<p>Or you might use it to write to the map. <\/p>\n<pre>\nm[2] = t; \/\/ create or update the item whose key is 2\n<\/pre>\n<p>The <code>[]<\/code> operator doesn&#8217;t know whether you&#8217;re going to read from or write to the result, so it has to come up with some sort of compromise. And sometimes the result of a compromise is something both sides dislike. <\/p>\n<ul>\n<li>    If the key is found in the map,     then return a reference to the existing value.     You can read that value or overwrite it. <\/li>\n<li>    If the key is not found in the map,     then create a new entry in the map consisting of the key     (moved, if movable) and a default-constructed value,     then return a reference to that freshly-created value.     You can read that value or overwrite it. <\/li>\n<\/ul>\n<p>This is different from how the <code>[]<\/code> operator works in C#, JavaScript, Java, Perl, PHP, Python, Ruby, pretty much every other programming language out there. In all those other languages, reading a value out of an associative array is a non-mutating operation. Trying to fetch an element whose key is <code>2<\/code> does not cause such an element to be created! <\/p>\n<p>But that&#8217;s what happens when you read from a <code>std::map<\/code> using the <code>[]<\/code> operator. <\/p>\n<p>Also, in those other languages, setting a new value in the associative array does not first create a throwaway value that gets immediately overwritten. <\/p>\n<p>But that&#8217;s what happens when you create a new key\/value pair in a <code>std::map<\/code> using the <code>[]<\/code> operator. <\/p>\n<p>Synthesizing a value in the <code>[]<\/code> operator means that you can accidentally fill your <code>std::map<\/code> with garbage entries when you really were just trying to see if an element was there. <\/p>\n<pre>\nstd::map&lt;int, std::unique_ptr&lt;T&gt;&gt; m;\n\nvoid RecolorizeIfPresent(int i)\n{\n  if (m[i]) { m[i]-&gt;Recolorize(); }\n}\n<\/pre>\n<p>If you call <code>Recolorize&shy;If&shy;Present<\/code> with an integer that is not in the associative array, the associative array will create an empty <code>std::unique_ptr&lt;T&gt;<\/code>, add it to the associative array, and then return it to you. Your <code>if<\/code> then converts that <code>std::unique_ptr&lt;T&gt;<\/code> to a <code>bool<\/code>, which says whether the <code>std::unique_ptr&lt;T&gt;<\/code> is managing a <code>T<\/code>, which in the case of a default-constructed <code>std::unique_ptr&lt;T&gt;<\/code>, will be &#8220;no&#8221;. <\/p>\n<p>The result is that your map gets clogged with empty <code>std::unique_ptr&lt;T&gt;<\/code> objects, one for each key you probed. If you aren&#8217;t expecting this behavior, you just created a memory leak: You didn&#8217;t expect the probe to create an entry in the map, so you aren&#8217;t going to have any code to clean out those empty entries. Those empty entries will just keep accumulating, slowly consuming more and more memory. <\/p>\n<p>Meanwhile, the code that is trying to add an entry to the map is also not too happy. <\/p>\n<pre>\nstd::map&lt;int, T&gt; m;\n\nm[2] = T(constructor_argument1, constructor_argument2);\n<\/pre>\n<p>It looks like this code creates a <code>T<\/code> object and puts it into the map under the key <code>2<\/code>. But actually, this code creates <i>two<\/i> <code>T<\/code> objects. It creates a default one when you say <code>m[2]<\/code> and there is no entry for <code>2<\/code>, and then it creates a second one when you construct one with <code>T(...)<\/code>. Finally, the assignment operator causes the first one to be overwritten by the second one, and then the second one is destructed. <\/p>\n<p>This can be quite expensive if <code>T<\/code> has a complex constructor, or if its constructor has observable side effects. <\/p>\n<pre>\nclass Screenshot\n{\npublic:\n  \/\/ The default constructor captures the entire screen.\n  Screenshot();\n\n  \/\/ Or you can specify a portion of the screen.\n  Screenshot(Rectangle const&amp; rect);\n\n  ...\n};\n\nstd::map&lt;Timestamp, Screenshot&gt; screenshots;\n\nvoid CaptureScreenRectangle(Rectangle const&amp; rect)\n{\n m[now()] = Screenshot(rect);\n}\n<\/pre>\n<p>Every time you call <code>Capture&shy;Screen&shy;Rectangle<\/code>, you actually take a screen shot of the entire screen (which will probably be slow and allocate a lot of memory), and then take a screen shot of the desired rectangle (which could very well be tiny), and then overwrite the giant screen shot with the small one. <\/p>\n<p>Note also that if the value type is not default-constructible, then the <code>[]<\/code> operator won&#8217;t work at all! <\/p>\n<pre>\nstruct Nope\n{\npublic:\n  Nope(int); \/\/ no default constructor\n};\n\nstd::map&lt;int, Nope&gt; m;\n\nm[2] = Nope(2); \/\/ does not compile\n<\/pre>\n<p>My personal recommendation is to avoid the <code>[]<\/code> operator. If you want to use an entry if it exists, then use <code>map::find()<\/code>. <\/p>\n<pre>\nauto item = m.find(2);\nif (item != m.end()) {\n DoSomethingWith(*item);\n}\n<\/pre>\n<p>If you expect the entry to exist, then use <code>map::at()<\/code>. <\/p>\n<pre>\nauto&amp; value = m.at(2); \/\/ throws if not found\n<\/pre>\n<p>If you want to create a brand new entry, but leave any existing entry intact, then use <code>map::insert<\/code> or <code>map::emplace<\/code> or <code>map::try_emplace<\/code>. <\/p>\n<pre>\nauto [item, inserted] = m.insert({ now(), Screenshot(rect) });\n\nauto [item, inserted] = m.emplace(now(), rect);\n\n\/\/ try_emplace doesn't even construct the Screenshot if an\n\/\/ entry already exists.\nauto [item, created] = m.try_emplace(now(), rect);\n<\/pre>\n<p>If you want to create a new entry or overwrite any existing entry, then use <code>map::insert_or_assign<\/code>.<\/p>\n<pre>\nauto [item, inserted] = m.insert_or_assign(now(), Screenshot(rect));\n<\/pre>\n<p>Just don&#8217;t use <code>[]<\/code>. <\/p>\n<p><b>Bonus reading<\/b>: <a HREF=\"https:\/\/www.fluentcpp.com\/2018\/12\/11\/overview-of-std-map-insertion-emplacement-methods-in-cpp17\/\">Overview of std::maps Insertion \/ Emplacement Methods in C++17<\/a>. <\/p>\n<p><b>Bonus chatter<\/b>: There have been <a HREF=\"https:\/\/groups.google.com\/a\/isocpp.org\/forum\/#!msg\/std-proposals\/09a4G1aXF8U\/Qs5yBO6QBAAJ\">various<\/a> <a HREF=\"https:\/\/groups.google.com\/a\/isocpp.org\/d\/msg\/std-proposals\/K7hIXITovAc\/w3VEAK3kBgAJ\">ideas<\/a> for <a HREF=\"https:\/\/groups.google.com\/a\/isocpp.org\/d\/msg\/std-proposals\/qcjpw-sidq0\/TZI7kiffXAIJ\">fixing this<\/a> problem with <code>[]<\/code>, but none of them work. <\/p>\n<p><b>Bonus viewing<\/b>: Louis Brandy calls this &#8220;<a HREF=\"https:\/\/www.youtube.com\/watch?v=lkgszkPnV8g&amp;t=7m3s\">The greatest C++ newbie trap<\/a>.&#8221; <\/p>\n","protected":false},"excerpt":{"rendered":"<p>It makes code easy to read, but things happen behind the scenes you may not be expecting.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-101072","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>It makes code easy to read, but things happen behind the scenes you may not be expecting.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/101072","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=101072"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/101072\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=101072"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=101072"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=101072"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}