[HN Gopher] My favourite C++ footgun
       ___________________________________________________________________
        
       My favourite C++ footgun
        
       Author : pabs3
       Score  : 99 points
       Date   : 2021-06-21 09:29 UTC (1 days ago)
        
 (HTM) web link (dustri.org)
 (TXT) w3m dump (dustri.org)
        
       | ot wrote:
       | In modern C++ the use of `new` and `delete` should be considered
       | smell, for this and other reasons.
        
       | fooker wrote:
       | That's not a valid evaluation order, afaik. You can not start
       | evaluating one argument, stop and switch to the other, then come
       | back to the earlier one.
        
         | MaxBarraclough wrote:
         | Do take a look at dvt's comment. Prior to C++17, it was a valid
         | evaluation order.
        
           | bregma wrote:
           | Prior to C++17 such an evaluation order would have violated
           | the standard. ISO/IEC 14882:2011 1.9/13 [Note:
           | Indeterminately sequenced evaluations cannot overlap, but
           | either could be executed first. -- end note]. The arguments
           | in a faction call are a comma-separated indeterminate
           | sequence (5.2.2/4 [Note: Such initializations are
           | indeterminately sequenced with respect to each other (1.9) --
           | end note]).
        
             | MaxBarraclough wrote:
             | > Prior to C++17 such an evaluation order would have
             | violated the standard
             | 
             | Herb Sutter disagrees. [0] The C++ legalese you've quoted
             | seems decisive, unfortunately Sutter's post doesn't mention
             | it.
             | 
             | See also [1][2].
             | 
             |  _edit_ quietbritishjim beat me to it, and with a better
             | informed comment to boot.
             | 
             | [0] https://herbsutter.com/2013/05/29/gotw-89-solution-
             | smart-poi... (ctrl-f for _known_ , and see also the comment
             | thread at the bottom of the page)
             | 
             | [1] https://stackoverflow.com/a/48844115/
             | 
             | [2] https://stackoverflow.com/a/46472497/
        
             | ectopod wrote:
             | Is there any version of C++ in which this interleaving is
             | permitted?
        
             | quietbritishjim wrote:
             | The full context to that second quote is:
             | 
             | > When a function is called, each parameter ([dcl.fct])
             | shall be initialized ([dcl.init], [class.copy],
             | [class.ctor]) with its corresponding argument. [ Note: Such
             | initializations are indeterminately sequenced with respect
             | to each other ([intro.execution]) -- end note ]
             | 
             | So it's only about initialisation of parameters from
             | arguments, not to the evaluation of arguments. (For example
             | if foo() takes a std::string parameter, and bar() returns a
             | const char*, then in the expression foo(bar()) the above
             | quote refers to the call to the string constructor, _not_
             | to the call to bar()).
             | 
             | The more relevant part is 5.2.2/8:
             | 
             | > [ Note: The evaluations of the postfix expression and of
             | the argument expressions are all unsequenced relative to
             | one another. All side effects of argument expression
             | evaluations are sequenced before the function is entered
             | (see [intro.execution]). -- end note ]
             | 
             | As others have said, this didn't change until C++17.
        
       | eklitzke wrote:
       | The author writes "this might be why there is std::make_shared",
       | but that's not really why it exists. When you create a
       | std::shared_ptr it needs to allocate memory for a control block
       | structure (which tracks the reference count) as well as the
       | memory for the object itself. If you write
       | std::shared_ptr<Foo>(new Foo) this will result in two memory
       | allocations, one for the Foo object and the other for the
       | std::shared_ptr control block. If you write
       | std::make_shared<Foo>() then a single allocation will happen
       | which has enough space for both the control block and the Foo
       | object, and then placement new is used to initialize the memory
       | for both structures. So std::make_shared exists to reduce the
       | number of memory allocations that are being made, not for the
       | reason suggested here.
        
         | quietbritishjim wrote:
         | It exists for both reasons.
         | 
         | That's why there's also a std::make_unique even though it
         | doesn't have a control block (although it was added later, but
         | that was just an oversight).
        
           | MaxBarraclough wrote:
           | > It exists for both reasons.
           | 
           | I believe this is correct. Here's [0] some old Boost
           | documentation on Boost's _make_shared_ which inspired the C++
           | standard 's _make_shared_. It mentions both reasons.
           | 
           | [0] https://www.boost.org/doc/libs/1_67_0/libs/smart_ptr/doc/
           | htm...
        
         | haldean wrote:
         | There's a crazy downside to make_shared that I learned recently
         | because of this: if you have a weak pointer to a shared thing,
         | and the refcount for the shared thing drops to zero, the weak
         | pointers will keep the allocation for the object "alive",
         | because they still need access to the remnant and the remnant
         | was created in the same allocation as the object so they can't
         | be freed separately. So now I only use make_shared if I know
         | for sure there won't be a weak_ptr pointing at it (or if the
         | base object has a relatively small memory footprint after it's
         | been destructed).
        
           | ot wrote:
           | Yeah, that's definitely something to be aware of. It's
           | usually not an issue as most objects have small footprint
           | (and any allocations they in turn hold would be released when
           | the _strong_ refcount goes to 0).
        
         | asveikau wrote:
         | I think the idea that you are also relying on a presumably well
         | tested library to get the exception corner cases right is also
         | noteworthy. Memory leaks on allocation failure are pretty
         | common in naive code, and a good thing to handle in a library
         | where it can get well thought out.
        
           | Koshkin wrote:
           | This is very important. Try to hide most of complexity in a
           | library, and then unit-test the hell out of it.
        
       | CraigJPerry wrote:
       | A capturing lambda is another way to leak a std::shared_ptr<> - i
       | feel like it's an even easier way to run into this kind of
       | footgun but i suppose it depends whether you typically use lots
       | of lambdas.
       | 
       | A reasonable solution in my case could be to use std::weak_ptr<>
       | instead but that wouldn't be useful here.
        
         | quietbritishjim wrote:
         | > A capturing lambda is another way to leak a std::shared_ptr<>
         | 
         | How so? Do you mean if you do exactly what the article says
         | when initialising the capture variables? Or something lambda
         | specific?
        
       | overgard wrote:
       | My (least) favorite footgun is "auto" when it comes to
       | references.
       | 
       | If you want to get a pointer from something, you would write
       | this:                   auto fooPtr = mywidget.getPtr();
       | fooPtr->doCoolStuff();
       | 
       | But we all know references are better than pointers right? So we
       | should just write...                  auto fooRef =
       | myWidget.getRef();        fooRef.doCoolStuff()
       | 
       | The problem is... this is valid and will probably not do what you
       | want. It will make a copy. What you want is actually
       | auto& fooRef = myWidget.getRef();
       | 
       | I once spent an entire day debugging a bizarre crash because of
       | that. I was asking for a reference to a scene graph, and what was
       | actually happening is I was getting a clone of the scene graph
       | (which was an object that couldn't be safely copied), and then
       | destroying a bunch of shared pointers when the function returned.
       | Fun times.
        
         | criddell wrote:
         | If you develop on Windows and aren't using Visual Studio 2019.
         | 
         | "auto fooRef = myWidget.getRef()" will get a little squiggle
         | under it to warn you that you are making a copy.
        
           | overgard wrote:
           | I am very happy tooling is helping to make these sorts of
           | mixups easier to find, although sadly C++ is such a hard
           | language to write tooling for that those kinds of nice
           | features are pretty rare. I remember back when I had to work
           | in Xcode a few years ago I could rarely even get basic
           | intellisense to work.
        
           | Koshkin wrote:
           | > _and aren 't using Visual Studio_
           | 
           | OK, I tried this in Notepad, and it did not work.
        
             | malkia wrote:
             | yeah, same with WordPad, but see Word would give you some
             | squiggles :)
        
         | amluto wrote:
         | > What you want is actually > > auto& fooRef =
         | myWidget.getRef();
         | 
         | That variant has issues if getRef() actually returns a
         | temporary object. You may want auto&&.
        
         | bregma wrote:
         | Wait, you had an object that was unsafe to copy, but you told
         | the computer it was safe by not deleting the copy constructor?
         | I guess it should have done what you wanted, not what you told
         | it.
        
           | contravariant wrote:
           | Yeah I learned that the hard way when I found out that
           | std::vector _apparently_ feels free to move your objects
           | around in memory for you, no matter if you point to them from
           | somewhere else.
           | 
           | I mean in hindsight it's obvious, but still not exactly what
           | I wanted to happen.
           | 
           | For reference, what I did does actually work, temporarily.
        
             | daniel-levin wrote:
             | I think if you come from the JVM / CLR world, you are so
             | protected by the runtime's patching up of references that
             | it might not even occur to you that a (raw) pointer to a
             | data structure's internals can dangle after the data is
             | moved around. The runtimes mentioned pause your code, move
             | things around and even compact the heap and your references
             | magically still point to what they did before!
        
             | tines wrote:
             | > Yeah I learned that the hard way when I found out that
             | std::vector apparently feels free to move your objects
             | around in memory for you
             | 
             | In standard terminology, this is described as "invalidating
             | iterators". There are a bunch of member functions in
             | std::vector that either do or don't invalidate iterators,
             | e.g. push_back(...) does but size() doesn't. And as the
             | name implies, if you call a function that invalidates
             | iterators, all your existing iterators/pointers/references
             | become invalid.
        
               | Kranar wrote:
               | Iterator invalidation is different from this. Iterator
               | invalidation, as the name suggests, only applies to
               | iterators. The problem OP was having had to do with
               | dangling references.
               | 
               | Some containers guarantee references won't dangle on
               | mutation such as unordered_map. An unordered_map may
               | invalidate iterators if objects are added or removed, but
               | will never result in dangling references (unless the
               | object is removed). That is, it is safe to have a pointer
               | to an object owned by an unordered_map and continue using
               | that pointer even after an iterator to that same object
               | is invalidated.
        
               | tines wrote:
               | Ah, interesting. Even within that dichotomy, I'd think
               | that if references aren't invalidated on mutation, then
               | dereferencing iterators would be valid, but incrementing
               | them would not be.
        
               | einpoklum wrote:
               | Iterators are historically a generalization of pointers.
               | A pointer is a kind of iterator.
               | 
               | I mean, literally. The iterator of an std::vector<T> (not
               | a bool vector) is a T*.
        
             | _huayra_ wrote:
             | This is why things like std::list still have value:
             | iterator stability. You even get this in most map/sets too.
             | 
             | But vector is usually what one should reach for unless one
             | has a good reason not to. The only danger really comes when
             | the lifetime of iterators and the thing they point to
             | become a bit decoupled, even due to insertion!
        
               | einpoklum wrote:
               | You can also use an offset instead of a pointer (or a
               | pointer to the object and and an offset into its heap
               | storage, or whatever).
        
           | Dylan16807 wrote:
           | > told the computer it was safe
           | 
           | > by _not_ deleting
           | 
           | I think there's a problem here.
        
           | overgard wrote:
           | Well, the scene graph code wasn't code I wrote, and this was
           | back in 2014. But sure, obviously it was an error on my
           | part... we are talking about footguns afterall, I'm the one
           | that pulled the trigger.
        
           | secondcoming wrote:
           | No, _everyone_ gets LHS auto wrong at the start. They don 't
           | realise it makes a copy. I have to point it out every other
           | code review.
        
             | jcelerier wrote:
             | The rule is simple: auto behaves like a template parameter
        
               | Kranar wrote:
               | There is nothing simple about that (not to mention it's
               | not actually true either).
        
               | Koshkin wrote:
               | Ah, templates... Universal references... Good stuff.
        
               | Kranar wrote:
               | Yep, except now we're supposed to call them forwarding
               | references because the motivation Scott Meyers had for
               | naming them universal references turned out to be
               | incorrect in subtle ways.
               | 
               | Even the notable experts get things wrong when it comes
               | to how complex and bloated C++ is.
        
           | suprfsat wrote:
           | Simply by using C++, you tell the computer all sorts of
           | things, such as "I know the entire language by heart", and "I
           | have a death wish".
        
             | owl57 wrote:
             | While C++ is a dangerous tool indeed, ignoring the rule of
             | 5 is analogous to ignoring the safety guidelines, not
             | simply using the tool.
        
               | yongjik wrote:
               | A C++ object can be perfectly safe to copy in one
               | situation and conjure up nasal demons in another
               | situation. I don't think rule of 5 would help in that
               | case.
        
         | decker wrote:
         | Sounds like the root cause was due to not obeying the rule of
         | five:
         | https://en.wikipedia.org/wiki/Rule_of_three_(C%2B%2B_program...
        
           | overgard wrote:
           | Probably! I don't remember the code exactly, this was back in
           | 2014 and I think most of the code I had to consume was
           | primarily written by a 24 year old out of college (smart of
           | course, but C++ takes a long time to get good at).
        
         | einpoklum wrote:
         | So, if I write:
         | 
         | int x; auto y = x;
         | 
         | do you expect y to be a reference? Surely not.
         | 
         | No, my friend, auto is for values, not references. You want a
         | reference - you have to say so.
         | 
         | (And same goes for rvalue references - auto&& .)
        
         | leetcrew wrote:
         | I too have fallen victim to this and similar traps. it's a good
         | idea to use a deleted copy constructor when you know it's not
         | safe to copy the object. you can't completely stop others (or
         | yourself) from doing bad things, but you can at least give them
         | a moment to reflect on what they are doing.
        
           | nyanpasu64 wrote:
           | I wish C++ copy constructors were explicit by default.
        
           | secondcoming wrote:
           | It's not about safety, it's about efficiency. It's usually an
           | unintended copy. For example, if your function `foo` returns
           | a `const std::string&` the code `auto x = foo()` creates a
           | copy.
        
         | jchw wrote:
         | As others have pointed out, the idiomatic solution here is to
         | delete the copy constructor if an instance is unsafe to copy --
         | however, I suspect the _reason_ why auto behaves differently
         | from other inference in requiring this to be explicit is
         | probably something along the lines of making it harder to have
         | a dangling reference. It's shockingly easy to wind up with a
         | dangling reference with fairly innocuous code, something like
         | QString().toUtf8().data() so maybe this makes sense. (Doesn't
         | help for that case since it's a pointer to raw data, but you
         | get the picture.)
        
           | nyanpasu64 wrote:
           | Worse yet is an object which _is_ safe to copy in some cases,
           | but you intended to not copy it at the moment, and you create
           | a dangling reference to a field within. I wish C++ copy
           | constructors were explicit by default. (But declaring the
           | copy constructor explicit turns off aggregate initialization,
           | so I can 't do that.)
        
           | overgard wrote:
           | I think, all things considered, the way it works is probably
           | the best way, since you might want to use "auto" to actually
           | make a copy. It's just very surprising, because when I have
           | explicit types I'm used to looking for that sort of error,
           | but with an auto I wasn't (at the time) used to looking for
           | that.
        
       | dvt wrote:
       | The nested function execution interleaving blew my mind (imagine
       | having to debug that), so I had to look it up. Apparently,
       | interleaving is prohibited in C++17 onward. So this:
       | 
       | > The second one is there since this is C++, priority() might
       | raise an exception, meaning that new Widget will be called, but
       | never passed to std::shared_ptr, and thus never deleted!
       | 
       | Is now impossible (thank God!). See a full SO discussion here[1].
       | Stuff like this makes me so happy I don't write C++ anymore, but
       | the gist of it is (from the standard):
       | 
       | > For each function invocation F, for every evaluation A that
       | occurs within F and every evaluation B that does not occur within
       | F but is evaluated on the same thread and as part of the same
       | signal handler (if any), either A is sequenced before B or B is
       | sequenced before A.
       | 
       | [1] https://stackoverflow.com/questions/38501587/what-are-the-
       | ev...
        
         | ncmncm wrote:
         | In three decades' use of C++, of all generations going back to
         | original cfront, I have never encountered any difficulty,
         | confusion, or actual problem arising from this phenomenon. It
         | is a favorite of language lawyers and people borrowing trouble.
        
         | TwoBit wrote:
         | > Is now impossible
         | 
         | I don't see how that is so. C++ 17 allows new Widget to
         | complete and then the priority() call to execute and throw
         | before both are passed to shared_ptr(), thus creating a leak.
         | Your cited example [1] doesn't leak because both arguments are
         | shared_ptr. Seems to me that C++ 17 does indeed solve this
         | latter case but not the former.
        
           | nyanpasu64 wrote:
           | > both are passed to shared_ptr()
           | 
           | priority() is _not_ passed to shared_ptr(), only to
           | processWidget().
        
           | dvt wrote:
           | Per [1]:
           | 
           | > evaluations of A and B are _indeterminately sequenced_ :
           | they may be performed in any order but may not overlap:
           | either A will be complete before B, or B will be complete
           | before A. The order may be the opposite the next time the
           | same expression is evaluated.
           | 
           | And:
           | 
           | > 21) Every expression in a comma-separated list of
           | expressions in a parenthesized initializer is evaluated as if
           | for a function call ( _indeterminately-sequenced_ )
           | 
           | Emphasis mine. What the above basically says is that in the
           | case of some function `f(A, B)`, the arguments `A`, and `B`,
           | are what's known as "indeterminately-sequenced" -- this mean
           | that their execution cannot be interleaved (overlap) -- but
           | they still individually execute in a non-deterministic order
           | (A before B, and sometimes B before A)!
           | 
           | With that said, the good news is that B can now never throw
           | in the middle of A, which is precisely what we have in OP's
           | example.
           | 
           | [1] https://en.cppreference.com/w/cpp/language/eval_order
        
             | _huayra_ wrote:
             | Whew thanks for looking this up! I was afraid I'd have to
             | add yet another entry to my gigantic C++ footguns.org
             | document. It's getting so big now that Emacs struggles a
             | bit to load it due to inline code examples!
        
               | shric wrote:
               | At first I thought that was a domain and not an org file.
               | :)
               | 
               | Looks like footguns.com is taken but footguns.org is
               | still available.
        
       | dale_glass wrote:
       | My favorite: slicing.
       | 
       | That one blew my mind when I found out. You can rip off a chunk
       | of an object by accident... and nothing happens whatsoever.
       | There's no warning from the compiler.
       | 
       | You can stuff a Derived into a std::list<Base>, it'll work just
       | fine, and if Derived happened to say, hold a pointer, it'll
       | happily vanish into the ether.
       | 
       | Besides the amount of confusion that can ensure from this, it
       | means you can have problems if you want to introduce inheritance
       | into a place that didn't have it before.
       | 
       | Especially if it's some sort of external component. You think
       | you're clever inheriting from a framework class and adding some
       | data on top? Nope, the internal structure you don't control
       | doesn't want to cooperate with that plan.
        
       | user-the-name wrote:
       | Well, here we have an entire thread of nothing but reasons to
       | never write anything in C++.
       | 
       | Jesus christ, what an utter mess of a language.
        
       | MaxBarraclough wrote:
       | One of my favourites too. Here's a good StackOverflow answer on
       | the topic. [0] Looking here [1] though, did something change in
       | C++17 to ameliorate things? _edit_ Turns out yes, see dvt 's
       | comment. _Second edit_ Apparently [2] I knew this 3 years ago and
       | forgot.
       | 
       | [0] https://stackoverflow.com/a/7693775/
       | 
       | [1] The final bullet-point in the Notes section of
       | https://en.cppreference.com/w/cpp/memory/shared_ptr/make_sha...
       | 
       | [2] https://stackoverflow.com/questions/38501587/what-are-the-
       | ev...
        
       | 2bitencryption wrote:
       | My favorite C++ footgun is creating a Vector with some items,
       | taking a reference to, say, &vec[3], then adding another item to
       | the vec, then trying to use the reference from the previous step.
       | 
       | If you write C++ it might be obvious what the problem is.
       | 
       | If you don't, this will absolutely ruin your entire day.
       | 
       | The worst part is, 95% of the time, it will probably work without
       | issue.
       | 
       | But eventually, pushing a new item to the Vector will trigger a
       | relocation of the whole vector, which will invalidate your
       | reference and bring down production. Have fun debugging that.
        
         | pizza234 wrote:
         | Interestingly, AFAIK also Golang suffers from something
         | similar: creating a slice from an array, then performing an
         | operation on the array, that causes resizing - the slice will
         | keep pointing to the old array data.
        
         | bentcorner wrote:
         | I can understand people used to a HLL running into this. It's
         | useful to read the documentation:
         | https://en.cppreference.com/w/cpp/container/vector/insert
         | mentions that references/iterators may be invalidated.
         | 
         | I'm not going to pretend that I understand everything on that
         | site (particularly anything about complex templates and things
         | like SFINAE) but often there's comprehensible stuff in there.
         | It can be really helpful.
        
         | flyingswift wrote:
         | What is the safe way to achieve the same result?
        
           | TylerGlaiel wrote:
           | store the index 3 as an int instead of &vec[3]
        
           | [deleted]
        
           | Kranar wrote:
           | The concept behind this is reference stability, and if you
           | need a collection that has stable references, you must
           | introduce a level of indirection, that is, instead of a
           | vector<T>, you use a vector<unique_ptr<T>> and then you can
           | take references as follows:                   auto& r =
           | *some_vector[0];
        
             | flyingswift wrote:
             | Thanks! I am just learning C++ for a new gig, and coming
             | from Javascript land, it is a lot to take in :)
        
           | yongjik wrote:
           | In addition to other answers, sometimes you do know the
           | final/max length of the vector when you construct it. In that
           | case reserve() can reserve the necessary space, and as long
           | as you stay under the limit all the addresses will remain
           | valid.
           | 
           | (Though it's still pretty brittle, so you may want to add a
           | ton of comments to warn yourself in the future...)
        
         | kaik wrote:
         | I kid you not, I spent a full working day debugging this exact
         | same issue (taking a pointer to a vector element, before adding
         | more elements). Very obvious if you understand how C++ and
         | vectors work, yet it took me forever to realize, and it was
         | miserable...
        
         | nicoburns wrote:
         | Yep. I learnt about this when I was learning Rust (which makes
         | this a compile-time error). I was very glad I didn't have to
         | learn this and the 100 other things like that seem to exist in
         | C++ the hard way!
        
         | krylon wrote:
         | You can run into the same problem in C, using malloc/realloc.
         | realloc, in fact, makes for a nasty footgun, too (and remains,
         | of course, available in C++).
        
           | turminal wrote:
           | Yes, but unlike with realloc and a custom dynamic array, C++
           | references and smart pointers and containers are half-smart
           | and do all sorts of things on their own. Knowing when they
           | will and when they will not be "smart" makes writing C++
           | really difficult sometimes.
        
         | duped wrote:
         | This, and the entire class of iterator invalidation bugs that
         | force you to memorize which collections are ok for which
         | applications.
        
           | nyanpasu64 wrote:
           | Rust takes the approach of flat-out not letting you mutate
           | _any_ collection while you have _any_ references to its
           | contents. It eliminates all dangling pointer bugs... I don 't
           | know if it rules out any useful use cases of collections
           | _with_ iterator stability. I think any C++ collection holding
           | unique_ptr _is_ stable (pushing to the collection doesn 't
           | invalidate the target of the unique_ptr), and Rust doesn't
           | have an safe ergonomic way to achieve that (perhaps
           | Pin<Box<MagicCell<T>>>, but we don't yet have a MagicCell
           | that makes &mut MagicCell<T> not noalias).
        
             | duped wrote:
             | The underlying pointer to a unique_ptr won't be invalidated
             | but the iterator might. Consider if you had a vector of
             | unique_ptrs and inserted into it within a for loop.
             | Depending on the implementation of the iterator this may
             | not be sound (if it's an index, you're probably ok, if it's
             | a pointer, you're screwed).
             | 
             | If you wanted to do the same in Rust it would be
             | Vec<Box<T>>. Mutating the collection won't invalidate the
             | pointers.
        
       | liquidify wrote:
       | Any raw new in code is a smell. I thought this was old news?
        
         | astrange wrote:
         | I've noticed that every few years when I look at C++ again,
         | everyone says this about whatever was correct last time.
        
       | _huayra_ wrote:
       | Jumping on the footgun bandwagon, one I had always known, but
       | never realized how bad of a design decision it was was "the c++
       | rvalue lifetime disaster" [0]. The talk linked in [0] has a
       | different conclusion, but describes the problem well.
       | 
       | Basically, the lifetime aspects of r/l-values are improperly
       | coupled to the "can I scavenge the internals?" aspect (via
       | const). R-values should not have been promoted to const-ref (I
       | think it was a legacy behavior?) because then this const-ref can
       | be passed around (e.g. returned) as if it were going to outlive
       | the original reference's frame!
       | 
       | This was the first time I realized what a mess C++ can be
       | (although I still use it a lot). This is a subtle error that I
       | have seen seasoned experts miss in code review (especially in
       | templated code). You can't work around it like you can the
       | underperforming parts of the stdlib (Abseil and Folly help out a
       | lot!). This promotion choice of auto&& -> auto const& is really
       | baked into the language and I don't see a path to change it.
       | 
       | The other "biggest foot cannon" has gotta be the incredibly
       | subtle ways one can violate the ABI, e.g. executable A linking
       | against libs B and C, each of which bundle an ABI-incompatible
       | implementation of the same parent dep D (versions 1.1 and 2.1).
       | You never know which type of object you're really passing around
       | with D::SomeType!
       | 
       | [0] https://quuxplusone.github.io/blog/2020/03/04/rvalue-
       | lifetim...
        
       | rileymat2 wrote:
       | If you try to use inheritance, object slicing by far.
       | 
       | I understand the practical reasons for it, but it feels very
       | wrong.
        
         | ncmncm wrote:
         | It only ever comes up in toy examples. Working programmers do
         | not encounter it, so in practice it is never the cause of a
         | problem.
        
       | yongjik wrote:
       | I think it's basically the same problem as unique_ptr? This is
       | the best explanation I've seen: https://herbsutter.com/gotw/_102/
        
       | jeffbee wrote:
       | This way to call a function smells wrong anyway. A shared pointer
       | to a new value is nonsense as a parameter, because at the point
       | of the call the pointer is not shared, it is unique! Since a
       | shared pointer is implicitly constructible from a unique pointer,
       | this makes more sense, assuming there are other call sites that
       | really intend to share ownership:                 void
       | frob(std::shared_ptr<T>);            main() {
       | frob(std::make_unique<T>());       }
        
         | ot wrote:
         | If you know that the pointer is going to be shared, it's more
         | efficient to use `make_shared` as it will use a single
         | allocation for both the object and the control block.
        
         | Koshkin wrote:
         | Well, usually you designate a pointer as 'shared' not only when
         | it happens to be already shared but also when it will be shared
         | later on.
        
         | Kranar wrote:
         | You've now forgone an important optimization opportunity for
         | absolutely no benefit.
        
       | throwaaskjdfh wrote:
       | Why does it seem like C++ is constantly replacing its subtle
       | hazards with even more subtle hazards? It's like they never go
       | away, they just turn into something more obscure.
        
         | plorkyeran wrote:
         | This is an example of a very old subtle hazard that has been
         | removed entirely (the ordering which causes problems is no
         | longer permitted in C++17).
        
           | [deleted]
        
         | AnimalMuppet wrote:
         | In addition to what plorkyeran said, "more obscure" can mean
         | either "harder to understand" or "less often encountered". The
         | second one is progress.
        
         | pshc wrote:
         | It's Stroustrup's Full Employment Theorem in action.
        
         | Kranar wrote:
         | Because the people involved with C++ have a toxic aversion to
         | learning from other languages. Anytime issues are brought up
         | the people involved with C++ standardization keep yapping the
         | line that "But C++ is not like <other language>." and then
         | proceed to implement a half-assed version of what other
         | languages have and then 2-3 years later people find all kinds
         | of footguns that could have simply been avoided had proper
         | research been performed.
         | 
         | The big issue is that becoming involved in the C++
         | standardization process is purposely arcane, requiring people
         | to physically travel to remote locations, miss time off work,
         | and spend a lot of money. There are literally substantial
         | language features added to C++ that are nothing more than the
         | work of maybe 3-4 people. These features could have benefited
         | enormously from having 100s, if not 1000s of potential
         | developers exercising use cases and contributing suggestions,
         | but the standardization process is heavily gated.
        
           | duped wrote:
           | I feel like this is a strawman.
           | 
           | 9/10 times when there is resistance to a particular feature
           | it's because implementing it breaks the ABI.
           | 
           | > requiring people to physically travel to remote locations,
           | miss time off work, and spend a lot of money.
           | 
           | It's a different kind of barrier to entry, but I would be
           | surprised if the attendees of the standards meetings aren't
           | being compensated for it.
        
         | Koshkin wrote:
         | Well, no doubt it all comes from good intentions. (With which,
         | you know, the road to hell is paved.)
        
       | towergratis wrote:
       | The real problem is not specific to C++, memory or shared
       | pointers, but as the author mentions later, the fact that
       | "function parameters evaluation order is unspecified".
       | 
       | The problem is similar in C as well.
       | 
       | `printf("%d, %d", i++, i++);` will give you different results
       | depending on the compiler.
        
         | Koshkin wrote:
         | The situation is even worse, due to the possible interleaving
         | of the execution of function calls.
        
           | towergratis wrote:
           | The root cause is the same
        
             | Koshkin wrote:
             | Well, except that C does not have exceptions (at least not
             | in the way that C++ does).
        
       ___________________________________________________________________
       (page generated 2021-06-22 23:00 UTC)