Constructors Shouldn't Throw(t3.at) |
Constructors Shouldn't Throw(t3.at) |
If you really wanted to go down the author's route, then you'd at least make the constructor private, and have a static method that returns a valid constructed object or an error, so you never have e.g. an "unconnected" database connection:
class DatabaseConnection {
public:
static std::expected<DatabaseConnection, ConnectionError> connect(/* ... */);
// ...
};
But I wouldn't even bother with this - it's exceptions with extra steps. I seem to remember that Stroustrop said in the Design and Evolution of C++ that the main reason for adding exceptions in the first place was to allow a failure result from a constructor.No, STL containers such as std::vector definitely do not assume (nor require) non-throwing constructors. They just do the right thing. Yes, cleanup is messier than it would otherwise be.
STL containers do require that your destructors not throw (but do not require that they be noexcept).
https://stackoverflow.com/questions/66459162/where-do-standa...
Background: https://quuxplusone.github.io/blog/2022/08/26/vector-pessimi...
I find it interesting that programming newbies are usually scared of C/C++ because "you have to deal with pointers" unlike in Java, Python, JavaScript etc.
It's interesting because all those languages of course use pointers and heap-allocated objects. The thing that sets the other languages apart is that they only allow pointer access and thus can make the language easier to reason about.
So what's "unusual" in C, C++ and Rust is really the parts that are not pointers, i.e. the ability to manage a data structure and its layout in memory directly, with all its consequences.
The given X should be subjected to the requirements necessary to create a valid Y. If the requirements fail, you have two main choices: Create an invalid Y, or reject X & return an error in however you want it to be returned.
The former leaks said rejections throughout the rest of the type's/object's code, & makes more work for me to parse through where & how the problem occurs.
The latter option creates a cleaner system altogether, due to rejecting sections of invalid states. Not all will be rejected, but the fact that some do get rejected already makes its workload comparatively lighter than the former.
I know which my lazy bum would pick.
It's a bad advice. It's preferred instead to have a connect function returning a database instance in a successful case or an error otherwise.
For example, if arg values can’t be negative, use an unsigned arg type.
No, I expect it to perform the functionality of bringing the object into a valid state - whatever functionality that takes. And if it can't, then throwing is the next option, because it's preferable to having an object in an invalid state.
I prefer to have constructors that can’t go wrong (except trivially for OOM that ends the whole process), and then manage their dynamic state after construction.
Look, for your problem, you probably want to do what the fine article said - to have "invalid connection" be a valid state of the object.
wrong
If we interpret "constructor" more generically as setup, one of my preferred languages, Go, does not have constructors, but we do have similar setup functions, which typically return errors, and we handle them as soon as possible without breaking out from the designed control flow.
I guess you're thinking more of package constructor methods, such as NewFoo, which tend to return the new object and an error value.
At one point they needed a custom build of AIX because its executable size limit of 1GB wasn't enough for the big. This was about 20 years ago, I suppose that seems quaint now.
for others context, bigs were the compiled binary deployed to a server, a composition of BB terminal functions, bin packing style