News for hackers. Stuff that matters.
Re:My favourite C++ footgun by ncmncm on Tuesday June 22, 2021 @06:53PM (#27598347)
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.
Re:My favourite C++ footgun by dataflow on Wednesday June 23, 2021 @12:46AM (#27600894)
I've actually run into this problem before. You just haven't, is all.
Re:My favourite C++ footgun by rualca on Wednesday June 23, 2021 @02:36AM (#27601478)
I really doubt this sort of hypothetical source of memory leaks was either significant or its root cause could be pinned to this. As this hypothetical scenario depends on an exception being thrown, your code would need to throw exceptions repeatedly for the memory leak to be a problem. If your code throws so many exceptions that not freeing the memory allocated during that code point becomes a problem then you have far more serious problems to fix than discussing language lawyer corner cases.
Re:My favourite C++ footgun by dataflow on Wednesday June 23, 2021 @02:40AM (#27601492)
> As this hypothetical scenario depends on an exception being thrown

It doesn't.

Exercise for the reader: detect evaluation interleaving without exceptions.

> If your code throws so many exceptions that not freeing the memory allocated during that code point becomes a problem

It need not manifest itself as a mere memory leak.

Exercise for the reader: make something happen beside a memory leak.

Re:My favourite C++ footgun by OskarS on Wednesday June 23, 2021 @09:25AM (#27604286)
> It doesn't

Yes it does. Of course you can detect different evaluation orders using side effects, but the fact that evaluation order is unspecified isn't generally a problem: if you write code like `a() + b()` and a() and b() has side-effects that depend on the order they're evaluated in, that is just a bad way of writing code. In any language.

The point here is that the compiler is allowed to interleave subexpressions of the two different arguments which causes a memory leak in seemingly safe code. That is a very unreasonable thing for the compiler to do, which is why it is a footgun. But not that it can ONLY happen if the second function throws in between the construction of the object with "new" and the construction of the shared_ptr: if the second function doesn't throw an exception, the construction of shared_ptr is guaranteed to happen.

Incidentally: those "exercise for the reader" comments are incredibly obnoxious.

Re:My favourite C++ footgun by dataflow on Wednesday June 23, 2021 @11:51AM (#27605926)
> isn't generally a problem

generally is the key word here.

> if you write code like `a() + b()`

The code doesn't have to look like that. Add e.g. some arguments...

Re:My favourite C++ footgun by CJefferson on Wednesday June 23, 2021 @04:28AM (#27602170)
I've frequently run into a variant of this:

    f(**x, g());
The number of stars evaluated before 'g()' (0, 1 or 2) varies between compilers, and the program I was working on would break if only one * was evaluated before g.
Re:My favourite C++ footgun by the-dude on Wednesday June 23, 2021 @04:34AM (#27602202)
Order of parameter eval is UB in plain C
Re:My favourite C++ footgun by dfox on Wednesday June 23, 2021 @05:11AM (#27602432)
It is unspecified, not undefined.
Re:My favourite C++ footgun by the-dude on Wednesday June 23, 2021 @05:13AM (#27602442)
Thanks for the correction. What does it mean?
Re:My favourite C++ footgun by palunon on Wednesday June 23, 2021 @05:36AM (#27602557)
The order of parameter evaluation is not specified by the standard, so if your program behavior is relying on it it may be incorrect. But relying on it doesn't instantly make your whole program completely meaningless, nasal demons and all.

> Certain other aspects and operations of the abstract machine are described in this International Standard as unspecified (for example, order of evaluation of arguments to a function). Where possible, this International Standard defines a set of allowable behaviors. These define the nondeterministic aspects of the abstract machine.

> Certain other operations are described in this International Standard as undefined (for example, the effect of dereferencing the null pointer). [ Note: this International Standard imposes no requirements on the behavior of programs that contain undefined behavior. —end note ]

Re:My favourite C++ footgun by bestouff on Wednesday June 23, 2021 @06:05AM (#27602723)
"Unspecified behavior" means something correct will happen, but you don't know what (in that case you can't rely on the execution order). "Undefined behavior" means something entirely incorrect may happen (crash or totally unexpected and bad behavior, so the term "nasal daemons" which means dragons coming out of your nose).
Re:My favourite C++ footgun by barrkel on Wednesday June 23, 2021 @05:32AM (#27602537)
If it was undefined, then anything could happen (nasal demons), but that would be silly: you wouldn't be able to safely call functions with multiple side-effecting arguments at all.

Unspecified means that you can't rely on the order of evaluation. "Anything" may not happen; the compiler controls the sequencing.

Re:My favourite C++ footgun by blake1 on Tuesday June 22, 2021 @09:00PM (#27599443)
Agreed. 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.

Re:My favourite C++ footgun by Koshkin on Tuesday June 22, 2021 @09:09PM (#27599510)
> 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.

Re:My favourite C++ footgun by remram on Tuesday June 22, 2021 @09:57PM (#27599896)
There is always the option to expose the "fallible construction" as a factory function instead of a constructor. Then you can return errors as needed.
Re:My favourite C++ footgun by TeMPOraL on Wednesday June 23, 2021 @04:55AM (#27602330)
That's an option, but now you're using a factory method instead of a constructor. It's a valid choice, particularly if you want to go for exception-free style - but GP's main point was that throwing from a constructor is not only not a footgun, it's actually the correct practice if you're writing RAII style.
Re:My favourite C++ footgun by jnwatson on Tuesday June 22, 2021 @10:50PM (#27600250)
And then every object has one more state.
Re:My favourite C++ footgun by Thorrez on Wednesday June 23, 2021 @02:20AM (#27601379)
You mean the factory function has to return some invalid state of the class? Not so.

The factory could return std::optional<Foo> or std::unique_ptr<Foo> or std::variant<Foo, Error> or absl::StatusOr<Foo> .

Re:My favourite C++ footgun by jcelerier on Wednesday June 23, 2021 @03:19AM (#27601732)
Your program will still have as many possible memory states as if you had a bool m_isValid; in your type - as now you don't store a Foo but an optional<Foo>.

It's an improvement in terms of code reuse but not in semantic simplification basically.

Re:My favourite C++ footgun by pwdisswordfish8 on Wednesday June 23, 2021 @03:27AM (#27601788)
You realise you can just unwrap an optional after checking it’s not nullopt, right?
Re:My favourite C++ footgun by jcelerier on Wednesday June 23, 2021 @04:07AM (#27602040)
Not if it's part of the state of your app ? E.g. a struct member. It also replaces an automated and enforced action by the compiler (exception being thrown) by a need for a manual check which feels very 1975
Re:My favourite C++ footgun by Thorrez on Wednesday June 23, 2021 @04:29AM (#27602176)
>Not if it's part of the state of your app ? E.g. a struct member.

I'm not sure what you mean. Here's an example with the output from std::optional being unwrapped and put in a struct member: https://godbolt.org/z/Ms4Kar18j

Also there's opt.value_or() which can use a default value in the case opt is nullopt.

>It also replaces an automated and enforced action by the compiler (exception being thrown) by a need for a manual check which feels very 1975

I think remram's goal was to avoid exceptions. So this is a criticism of remram's goal rather than the method to achieve that goal.

Also, std::optional can integrate with exception-producing code because opt.value() throws an exception if opt is nullopt.

Re:My favourite C++ footgun by jcelerier on Wednesday June 23, 2021 @08:38AM (#27603763)
> I'm not sure what you mean. Here's an example with the output from std::optional being unwrapped and put in a struct member: https://godbolt.org/z/Ms4Kar18j

And now you have to do this for every type that have some amount of preconditions. I don't understand in which universe this is sane - more code == more bugs.

e.g. here's what one would write with exceptions: https://godbolt.org/z/evMsh6383 (and, to be honest, if one is writing e.g. a command-line tool and not a reactive gui app, most likely https://godbolt.org/z/eW8YM5Gz4 as your OS will catch the thing anyways and then it's just a `coredumpctl gdb` / "open in debugger" away) ; you get the same guarantee of never having an invalid Foo but at much less mental cost.

It also prevents you of having aggregates as all the "parent" owner class will need their own wrapping in optional + private ctor, in case a sub-sub-sub-sub field would fail.

e.g. given

    struct DomainObject {
      Foo mainFoo;
      Foo secondaryFoo;
      int whatever;
    };
    struct GameState {
      std::vector<DomainObject> objects;
    };
now you can't just do DomainObject{initForMainFoo, initForSecondaryFoo, 123}; or DomainObject{.mainFoo = "", .secondaryFoo = "", .whatever = 123}; anymore, even less GameState{{DomainObject{...}, DomainObject{...}}; which is how modern C++ is meant to be used
Re:My favourite C++ footgun by pkolaczk on Wednesday June 23, 2021 @11:44AM (#27605838)
Exception handling may indeed end up with less code, but often it is because the developers forgot to handle most error situations properly. And that's my biggest gripe with exceptions, particularly the unchecked ones, which can just pop up from 100 layers below. I've just seen it too many times when developers just throw exceptions and let them bubble up to a place where there is so little context that the final error message presented to the user is near useless.

On the other hand return values (using something like a try/either monad) have the property of forcing the developer to think about the unhappy path in every layer. Maybe a bit more code, but the quality of diagnostics - priceless.

IMHO exceptions/panics should be reserved for bugs only (eg assertions), and return values for all the other error handling caused by user input or environment.

Re:My favourite C++ footgun by jcelerier on Wednesday June 23, 2021 @12:04PM (#27606090)
> I've just seen it too many times when developers just throw exceptions and let them bubble up

but it's the whole point ! that's exactly why exceptions are good !

> to a place where there is so little context that the final error message presented to the user is near useless.

there are only two valid cases: there's an error that you know you are able to recover from, you did recover, and the users does not see a message and never knows that an error happened, OR the user sees the message "The program has encountered an error. A backup of your data has been saved in c:/foo/. The program will now exit.".

Anything else makes software terrible to use. If you have logging and recovery to do, use your logging architecture and core dumps, don't misuse exceptions for that.

> On the other hand return values (using something like a try/either monad) have the property of forcing the developer to think about the unhappy path in every layer. Maybe a bit more code, but the quality of diagnostics - priceless.

Except what happens in practice is that people just put panics everywhere because most of the time there is no meaningful action one can take at the layer where the implementation happens. Actual examples in servo: https://pastebin.com/0DAFU9vS

I also invite you to run the following command:

    $ cd ~/.cargo
    $ rg panic! | wc -l
I get 2987 as a result. Choosing to program like this instead of using exceptions which at least can be recovered by the person who will use your lib, is literally hateful towards users.

I've not seen Go code to be meaningfully different either.

> IMHO exceptions/panics should be reserved for bugs only (eg assertions), and return values for all the other error handling caused by user input or environment.

Sure, this is the community consensus in C++. It's the Python people who use exceptions to get out of for loops :)

But the most important rule is that - invalid state must not be representable in the program. There must be no way to get an "invalid" object (unless the object being invalid is part of its possible domain, but this is exceedingly rare when one isn't interfacing with a 1985 C API)

Re:My favourite C++ footgun by pkolaczk on Wednesday June 23, 2021 @03:41PM (#27609080)
> but it's the whole point ! that's exactly why exceptions are good !

Good for what? For crashing the program with a stacktrace? Probably. But for regular error handling - that's a terrible thing. They break normal control flow by adding a parallel / alternative control flow. Now with them you have to think on what happens if any of the code you call suddenly throws an exception and the number of potential control flow paths increases drastically. They are not referentially transparent and it is nearly impossible to do functional programming with them (they introduce side effects everywhere). You can also end up in a really bad situation when a destructor throws (typically crash).

> I get 2987 as a result. Choosing to program like this instead of using exceptions which at least can be recovered by the person who will use your lib, is literally hateful towards users.

But panics are Rust equivalent of exceptions. That's the whole point of them. They are used for exceptional cases, which are impossible to recover from (which means the only way to deal with is to dump core and terminate) and most of the time preventable (you can always avoid dividing by zero or going out of array bounds).

It looks like we agree they should not be used for regular errors, like e.g. file not found. I'd not like my word processor to terminate with a core dump when it couldn't load the file from the disk. In this case return values are way better error handling mechanism.

> invalid state must not be representable in the program

Returning an option/try/either strikes that goal just as well as exceptions.

Re:My favourite C++ footgun by fpoling on Wednesday June 23, 2021 @04:05PM (#27609468)
You can recover from panic! in Rust on the thread level.
Re:My favourite C++ footgun by pkolaczk on Wednesday June 23, 2021 @04:14PM (#27609630)
Yeah, I know I can recover, that's why I said they are equivalent of C++ exceptions (which can be caught as well). But this is not a recommended idiomatic way of handling expected errors, e.g. input errors, improper configuration, I/O errors etc.
Re:My favourite C++ footgun by rualca on Wednesday June 23, 2021 @02:42AM (#27601508)
> And then every object has one more state.

No, not really. Either the allocation followed the happy path, or one of the expected failure modes kicked in.

And the supposed footgun example boiled down to failing to handle one of the failure modes.

I mean, think about it: std::make_shared is a factory method, isn't it?

Re:My favourite C++ footgun by Koshkin on Tuesday June 22, 2021 @11:28PM (#27600471)
Not always, e.g. not if your class includes a member that is a reference (and, say, aggregate initialization is not allowed for one reason or another).
Re:My favourite C++ footgun by Thorrez on Wednesday June 23, 2021 @02:32AM (#27601442)
It seems to work for me: https://godbolt.org/z/68s8qsP6K
Re:My favourite C++ footgun by layer8 on Wednesday June 23, 2021 @11:29AM (#27605644)
That doesn’t compose in a class hierarchy, though.
Re:My favourite C++ footgun by remram on Wednesday June 23, 2021 @01:18PM (#27607108)
That's a good point... You might be able to work around that using a template function factory e.g. `make_foobar<ChildrenFooBar>()`. I'm not sure what's a good pattern here, I don't write much C++.
Re:My favourite C++ footgun by gpderetta on Wednesday June 23, 2021 @01:53PM (#27607661)
As long as the every element of the hierarchy is noexcept-movable it still composes. It is just very cumbersome.
Re:My favourite C++ footgun by fpoling on Wednesday June 23, 2021 @04:39AM (#27602236)
You can also use out parameters to tell about errors.
Re:My favourite C++ footgun by johannes1234321 on Wednesday June 23, 2021 @02:19PM (#27608048)
When doing that the better approach is to use a factory function wrapping this.

However mind that if you don't throw from C++'s perspective the object is fully created, so that the destructor will run.

If you throw an exception from the contractor, the object won't be fully created.

Re:My favourite C++ footgun by fpoling on Wednesday June 23, 2021 @03:46PM (#27609158)
In practice I have not found that running destructor is problematic. In many cases the destructor is the default one that just destructs the fields. When the destructor should be non-default the constructor can leave the object in the move out state either explicitly or via hacks like using T tmp(std::move((*this)) on the error return in the constructor.
Re:My favourite C++ footgun by Koshkin on Wednesday June 23, 2021 @09:22AM (#27604246)
This won't work with third-party callers.
Re:My favourite C++ footgun by cglodt on Wednesday June 23, 2021 @02:04AM (#27601282)
Throwing from a constructor is a fundamental way to make it impossible to construct invalid instances of a class. It's great, especially for immutable classes. Throwing from constructor + immutability = no invalid states ever.
Re:My favourite C++ footgun by LAC-Tech on Wednesday June 23, 2021 @02:22AM (#27601387)
> 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.

Yeah there's definitely criticisms you can throw at C++, but this always struck me as stupid. You could also write an `add` method that divides in Java.

I've never liked the idea of 'operators' in the first place, tbh. Scheme opened my eyes in that they're all just procedures. And then with Smalltalk and Scala I saw that they can all just be methods as well.