4 ms·
Have you heard the phrase "death by 1000 cuts"? That's what programming in modern C++ is like. I agree they don't quite make the case for "doom" but it's stil
by mark-r 5y ago
Have you heard the phrase "death by 1000 cuts"? That's what programming in modern C++ is like. I agree they don't quite make the case for "doom" but it's still not a pretty picture.
- qayxc 5y agoThis image is mostly painted by people who forgot to stop. By that I mean stop using every feature and detail because it's there. C++ can be used like a much better C (smart pointers, references, const-correctness, generics via templates) and most of the complications can be ignored that way. Premature optimisation is another big factor. If there's no measurable performance bottleneck, things like move-semantics don't even matter in practise for example.
- nyanpasu64 5y agoDoes the "much better C" include constructors are not? Smart pointers (which you include) are primarily designed for types with ~Type destructors, and difficult to use with C-style types with free functions for deallocation. And ~Type is difficult (unsure if impossible) to use without constructors, through either new Type or placement new into malloc'd memory. There is the trick of defining a passthrough constructor and moving initialization logic to a separate function calling the constructor. But if you want placement initialization, I'm unsure if C++ allows the C trick of casting malloc'd memory into Type * and passing it into a placement-init function that isn't a Type() constructor (pretty sure it's illegal to do so if Type contains std::vector, or shared_ptr, possibly unique_ptr). And constructors cannot report failures outside of out-parameters or exceptions, both of which were criticized in the article. So "most of the complications can be ignored that way" isn't true in my experience. If you want fallible placement initialization, perhaps you could define a static method or free function taking a void* to malloc'd memory, which performs some logic then conditionally calls a placement constructor on the void*, then [[nodiscard]] return whether you succeeded. I find this awkward and not much better than C. Personally I give up placement initialization and return a std::optional<T>. >references... generics via templates Implicit copy constructors are a footgun not found in C, and references decay to copies at every opportunity (auto, templates).
- qayxc 5y agoI don't understand the obsession with constructors. Placement new exists, but I'm not sure that's what you asked w.r.t. placement initialisation, which doesn't make much sense for non-trivial types anyway. Fallible placement initialisation into allocated memory is something that I honestly don't know a good use case for. It's certainly not something that's very common and I never encountered it. It's just one of those things that smell like a design that doesn't suit the problem. Do you by any chance have an example for when this would be useful? I'm curious to learn about this. Implicit copy constructors can be disabled if need be by simply deleting them (Type(Type const&) = delete). It's up to the programmer since the compiler simply cannot know. As far as auto and copies goes: almost always auto&& and when in doubt, spell it out (i.e. don't use auto). Again, not knowing the semantics isn't really an argument against the language. It also again boils down to a case of premature optimisation if copies concern you even though there must be a profiling log pointing to that being a problem first.
- nyanpasu64 5y ago> I don't understand the obsession with constructors. The article describes how smart pointers are more difficult to use for opaque/POD structs without destructors. Correct me if I'm wrong, but I think types with destructors generally require constructors too (though you can sometimes rely on default destructors/constructors, by holding fields which themselves have destructors/constructors). > Fallible placement initialisation into allocated memory is something that I honestly don't know a good use case for. It's certainly not something that's very common and I never encountered it. Understandable. Rust lacks any safe means for placement initialization (even infallible), resulting in stack overflows (often disappearing in optimized release builds) when allocating large heap arrays (because it has to stack-allocate the array, then memcpy to the heap), eg. https://github.com/rust-lang/rust/issues/53827 https://github.com/rust-lang/rust/issues/53827. I haven't encountered this firsthand though. > It also again boils down to a case of premature optimisation Copying can be a correctness issue as well as performance. One time I wrote a function which took an object by copy (when I should've passed by reference), and returned a reference to the object, resulting in a use-after-free. Additionally mutating a copy doesn't mutate the original object, unlike a non-const reference.
- lpapez 5y agoThis is not how programming works in large corporations. If a feature is there, the people reviewing your code will want you to use it. Even though most C++ shops have a specific subset of "allowed C++", the language itself nudges you to think about copying stuff - that's why it ha all the RVO, move semantics, ptr stuff inside. Having switched to Go recently, it feels so good when my teammates don't care (and don't know) that the variable in for-loop is always copied. We just care about solving the problem. In C++ you have 4 different ways to declare a for-loop variable, and you need to think about selecting the "correct" one, even if you're not doing performance critical code.