11 ms·
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
by dvt 5y ago
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-evaluation-order-guarantees-introduced-by-c17/46472497#46472497 https://stackoverflow.com/questions/38501587/what-are-the-ev...
- TwoBit 5y ago> 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 5y ago> both are passed to shared_ptr() priority() is not passed to shared_ptr(), only to processWidget().
- dvt 5y agoPer [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 https://en.cppreference.com/w/cpp/language/eval_order
- _huayra_ 5y agoWhew 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 5y agoAt first I thought that was a domain and not an org file. :) Looks like footguns.com is taken but footguns.org is still available.
- _huayra_ 5y agoGotta register a 501c3 to help save poor C++ programmers from the many footguns. Donate today!
- nneonneo 5y agoYour footguns.org file legitimately sounds like something that other C++ programmers would find useful. I for one, being a rank novice at the language (despite attempting to use it for the last ten years!), would certainly appreciate a reference like that.
- _huayra_ 5y agoMost of it is basically from cppcon talks and Scott Meyer's books, but the advice I basically give to incoming C++ programmers today is this: * Start with C++20 unless you have a very good reason not to. Not only does it obviate crufty old things like SFINAE (concepts!), but it includes a ton of usability fixes (e.g. reflexive operator== -> I only have to write the code for MyType == MyOtherType in order to get both that version and MyOtherType == MyType). A lot of lambda behavior has really been cleaned up too (e.g. capturing `this` has fewer corner cases). * start with Google's coding guidelines [0], as they've been developed to avoid many footguns (the bottom line depends on it). Once you understand things better, keep removing these rules (e.g. about exceptions, const& parameters; many of these rules are good for big teams, but annoying to follow for individuals) * The CppCon "back to basics" talks are absolute goldmines, and the speakers usually tell you if some technique has been outdated as of the recording. Some things in certain books, although valuable, may be outdated based on later revisions to the language. * The best notes to take are usually some structure list followed by a bunch of godbolt links with examples, e.g. this one I made to demonstrate the reflexive operator== behavior [1] [0] https://google.github.io/styleguide/cppguide.html https://google.github.io/styleguide/cppguide.html [1] https://godbolt.org/z/scPdcaWT7 https://godbolt.org/z/scPdcaWT7
- TwoBit 5y agoOK, I must have mistaken what the original was, because I thought it was like this: processWidget(new Widget, priority()); I think I saw the above code elsewhere.
- ronyclau 5y agoIIRC, this may also leak when `priority()` throws due to evaluation order. (Not exactly sure now, as I now always use `make_shared` whenever possible.) However in the Effective C++ example, the `shared_ptr` constructor gave a false sense of security as it seemed the `new`-ed `Widget` was always managed by the smart pointer from its allocation.
- ncmncm 5y agoIn 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.
- blake1 5y agoAgreed. This code just looks weird to me. Throwing from a constructor? Holding the result of new() in a temporary? This is just asking for trouble. Another favorite “criticism” I have of C++ goes along the lines of “but someone could overload operator+ to be division!” I have only seen that as a contrived critic’s example, or a joke.
- Koshkin 5y ago> Throwing from a constructor? Actually, that’s what you are supposed to do, as this is the only way for a constructor to let the caller know that something went wrong.
- remram 5y agoThere is always the option to expose the "fallible construction" as a factory function instead of a constructor. Then you can return errors as needed.
- cratermoon 5y agoThere was a post here not too long ago about how C/C++ compiler writers have consistently taken the wiggle room of "undefined behavior" to do terrible, terrible, damage to otherwise fine code. https://news.ycombinator.com/item?id=27221552 https://news.ycombinator.com/item?id=27221552
- saagarjha 5y agoIn this case there is no undefined behavior, the order is just unspecified.
- cratermoon 5y ago> unspecified how is that different from undefined?
- saagarjha 5y agoThe existence of undefined behavior indicates that the program is invalid. Unspecified behavior is just something that is not precisely defined.
- Kranar 5y agoUndefined behavior is unbounded in both space and time in terms of program semantics, that is, a program that will engender undefined behavior may do so at any point during the course of program execution (unbounded in time) as well as may be observed within any memory region (unbounded in space). A colloquial way of saying this is that a compiler is free to do anything it wants anytime it wants in the presence of undefined behavior. Unspecified behavior is bounded both in space and time. Unspecified behavior may only affect program semantics within the statement in which it is engendered and may only be observed within the region of memory that is written to or read from. It is often said that undefined behavior is invalid, and one can sympathize with that statement as it's commonly repeated, but it's not true. All the standard says about undefined behavior is that the standard imposes no requirements about program behavior, it does not state that the program is invalid or that the program must behave in some kind of invalid manner. Almost all non-trivial C++ programs engender undefined behavior in a manner documented by the compiler and do so in a safe and principled way.
- armchairhacker 5y agoI always wonder if compilers actually do weird optimizations taking advantage of semantics like those. Another example is when undefined behavior occurs, like (iirc) signed integer overflow. Compilers can technically do anything they want, including weird things. But how often does that actually happen?
- blake1 5y agoDifferent architectures have their own function passing convention. It’s convenient to evaluate left-to-right, pushing as you go, if that matches the arch. And vice versa.
- tsimionescu 5y agoThat would explain why you want it implementation-defined, but leaving it indeterminate means that the same piece of code is allowed to have different evaluation orders each time it is encountered. This is much stranger, and very specific to C and C++, I don't think there is any other language that doesn't specify the order of execution.
- mannerheim 5y agoHaskell? Expressions have to be evaluated in order to evaluate expressions that depend on them, but otherwise the compiler is free to decide the order of evaluation. > In this case, it's pretty easy to see that 3 will have to be printed after both 1 and 2, but it's unclear whether 1 or 2 will be printed first. The reason for this is evaluation order: in order to evaluate one + two, we'll need to evaluate both one and two. But there is nothing telling GHC which of those thunks should be evaluated first, and therefore GHC is at full liberty to choose whichever thunk to evaluate first. https://wiki.haskell.org/Evaluation_order_and_state_tokens https://wiki.haskell.org/Evaluation_order_and_state_tokens
- tsimionescu 5y agoWell, Haskell's lazy execution model and implicit purity makes this far different from C++. The examples there all rely on the guarantee-breaking unsafePerformIO, while in C++ you can break your program with perfectly safe,c normal C++ code, such as creating a shared pointer and throwing an exception. Note that in Haskell evaluation order is not guaranteed anywhere, even between separate statements, whereas in C++ it is basically guaranteed everywhere except function calls.