14 ms·
Initialization in C++ is bonkers (2017)
- gnabgib 1y agoSmall discussion at the time (42 points, 6 comments) https://news.ycombinator.com/item?id=14532478 https://news.ycombinator.com/item?id=14532478 Related: Initialization in C++ is Seriously Bonkers (166 points, 2019, 126 points) https://news.ycombinator.com/item?id=18832311 https://news.ycombinator.com/item?id=18832311
- deleted 1y ago[deleted]
- deleted 1y ago[deleted]
- nlehuen 1y agoNot to worry, there is a 278 page book about initialization in C++! https://leanpub.com/cppinitbook https://leanpub.com/cppinitbook (I don't know whether it's good or not, I just find it fascinating that it exists)
- nitrogen99 1y agoWell, authors are incentivized into writing long books. Having said that it obviously doesn't take away the fact that C++ init is indeed bonkers.
- harry8 1y agoWhat would be the incentive for making this a long book? Couldn't be money.
- Analemma_ 1y agoI imagine if I'd managed to actually memorize all of C++'s initialization rules, I'd probably have to write a book too just to get it all out, or I'd lose my sanity.
- sph 1y agoThen you can proudly put “C++ initialization consultant” on your resumé and get paid $1000 a day fixing class constructors at Fortune 500 companies.
- codr7 1y agoThere are no limits to how much of an specialized expert you can become in C++. Knowing all of it just isn't possible from my experience.
- jcelerier 1y agoIt is actually. It's been shown that longer books make more sales as they are considered more trustworthy, so authors are incentivized to artificially drag them longer than they actually require
- harry8 1y agoEver written one? How much did you make?
- bluGill 1y agoThe money isn't from book sales. The money is you can charge higher consultant fees because "you wrote the book". If you don't play the game of course you won't make money, but writing a book is one step. (the full game has lots of different paths, there are other ways to make a lot of money without writing a book)
- harry8 1y agoright so no money in increased sales, which was obviously the point of the comment.
- nitwit005 1y agoImagine you're in a world where magazines are dead, but books are still a thing, and stores won't stock a thin book.
- bhk 1y agoWow! Exhibit 1 for the prosecution.
- kazinator 1y agoC++ doesn't have initiation hazing rituals, but initialization hazing rituals. (One of which is that book.)
- codr7 1y agoThat's what I've been saying, every line of C++ is a book waiting to be written.
- ts4z 1y agoThis is a specialization of the general statement that C++ is bonkers.
- lblume 1y agoUnlike Rust, C++ at least has specialization...
- MichaelMoser123 1y agoand putting structure instances into an array so that you can refer to them via indexes of the array entries (as the only escape from being maimed by the borrow checker) is normal?
- ts4z 1y agoC++ would be bonkers even if Rust did not exist.
- MichaelMoser123 1y agoyou have a point. The usual approach was to choose a subset of C++ features, so it becomes appropriate for a given project. But yes, the language is huge - as it tries to suite everyone but no one in particular (which is insane)
- timewizard 1y ago> Explicitly initialize your variables, and if you ever fall in to the trap of thinking C++ is a sane language, remember this It's a systems language. Systems are not sane. They are dominated by nuance. In any case the language gives you a choice in what you pay for. It's nice to be able to allocate something like a copy or network buffer without having to pay for initialization that I don't need.
- vacuity 1y agoI think in this case it's not amiss to mention Rust. Rust gives a compile error if it's not certain a variable is initialized. Option is the standard dynamic representation of this, and works nicely in the context of all Rust code. MaybeUninint is the `unsafe` variant that is offered for performance-critical situations.
- tialaramex 1y agoTo clarify for anybody else following along (I assume you knew), the type MaybeUninit isn't unsafe (Rust doesn't have unsafe types), only its method named assume_init and related APIs are unsafe. That's because only this feature introduces the potential for safety problem, which is amusing because it doesn't actually do anything per se, it will often emit zero CPU instructions. It's unsafe because before we called this function we had a MaybeUninit<T> and, well as it said, maybe it isn't initialized, so nothing will assume it is. But once we assume_init, we've got a T, all the code working with the T, including safe Rust code, is entitled to ignore any scenarios that could arise if it were not, in fact, initialized properly. Despite not in some sense "doing" anything, the unsafe call was critical.
- gosub100 1y agoThat may have made sense in the days of < 100 MHz CPUs but today I wish they would amend the standard to reduce UB by default and only add risky optimizations with specific flags, after the programmer has analyzed them for each file.
- jcelerier 1y ago
- vitaut 1y agohttps://www.reddit.com/r/ProgrammerHumor/comments/8nn4fw/forrest_gump_learns_c/ https://www.reddit.com/r/ProgrammerHumor/comments/8nn4fw/for...
- e-dant 1y agoLet the language die, hope it goes quicker than cobol.
- gosub100 1y agoCOBOL is alive and well. Why would a company rewrite a codebase that has decades of error free functionality? What do they get?
- cheema33 1y ago> Why would a company rewrite a codebase that has decades of error free functionality? What do they get? All well and good if it is something you do not have to modify/maintain on a regular basis. But, if you do, then the ROI on replacing it might be high, depending on how much pain it is to keep maintaining it. We have an old web app written in asp.net web forms. It mostly works. But we have to maintain it and add functionality to it. And that is where the pain is. We've been doing it for a few years but the amount of pain it is to work on it is quite high. So we are slowly replacing it. One page at a time.
- gosub100 1y agothe insurance companies running COBOL don't care. it's cheaper to pay a cowboy $X00,000/yr to keep the gravy dispenser running than trying to modify it. by definition, this is code that's been in use for decades. Why change it?
- bdangubic 1y ago“quicker than cobol” means it will die in the next 100 years (maybe) :)
- jimbob45 1y agoI suspect the committee agrees with you. I think they’ve anticipated a competitor coming to kill C++ for two decades now and see themselves as keeping C++ on life support for those who need it. It’s shameful that there’s no good successor to C++ outside of C# and Java (and those really aren’t successors). Carbon was the closest we came and Google seems to have preemptively dropped it.
- beached_whale 1y agoAnd for the most part it does what you expect.
- jandrewrogers 1y agoI largely prefer modern C++ as systems languages go but there is no getting around the fact that the initialization story in C++ is a hot mess. Fortunately, it mostly does what you need it to even if you don't understand it.
- vjvjvjvjghv 1y agoAnd sometimes it doesn’t do what you think it does.
- waynecochran 1y agoThis idea that everything must be initialized (i.e. no undefined or non-deterministic behavior) should never be forced upon a language like C++ which rightly assumes the programmer should have the final say. I don't want training wheels put on C++ -- I want C++ do exactly and only what the programmer specifies and no more. If the programmer wants to have uninitialized memory -- that is her business.
- anon-3988 1y agoIf they want the program to do exactly what is told they won't get to have optimization.
- waynecochran 1y agoThat's the whole point of UB -- it leaves open more possibilities for optimization. If everything is nailed down, then the options are more restricted.
- dminik 1y agoIt doesn't really seem worth it. https://web.ist.utl.pt/nuno.lopes/pubs/ub-pldi25.pdf https://web.ist.utl.pt/nuno.lopes/pubs/ub-pldi25.pdf > The results show that, in the cases we evaluated, the performance gains from exploiting UB are minimal. Furthermore, in the cases where performance regresses, it can often be recovered by either small to moderate changes to the compiler or by using link-time optimizations.
- GrantMoyer 1y agoThe problem is that the initialization semantics are so complex in C++ that almost no programmer is actually empowered to exercise their final say, and no programmer without significant effort. And that's not just said out of unfamiliarity. I'm a professional C++ developer, and I often find I'm more familiar with C++'s more arcane semantics than many of my professional C++ developer co-workers.
- vjvjvjvjghv 1y agoThe dev should have the option to turn it off but I think that removing a lot of undefined and non deterministic behavior would be a good thing. When I did C++ I initialized everything and when there was a bug it could usually be reproduced. There are a few cases where it makes sense performance wise to not initialize but those cases are very small compared to most other code where undefined behavior causes a ton of intermittent bugs.
- shadowdev1 1y agoHeh, low comments on C++ posts now. A sign of the times. My two cents anyway. I've been using C++ for a decade. Of all the warts, they all pale in comparison to the default initialization behavior. After seeing thousands of bugs, the worst have essentially been caused by cascading surprises from initialization UB from newbies. The easiest, simplest fix is simply to default initialize with a value. That's what everyone expects anyway. Use Python mentality here. Make UB initialization an EXPLICIT choice with a keyword. If you want garbage in your variable and you think that's okay for a tiny performance improvement, then you should have to say it with a keyword. Don't just leave it up to some tiny invisible visual detail no one looks at when they skim code (the missing parens). It really is that easy for the language designers. When thinking about backward compatibility... keep in mind that the old code was arguably already broken. There's not a good reason to keep letting it compile. Add a flag for --unsafe-initialization-i-cause-trouble if you really want to keep it. C++, I still love you. We're still friends.
- juliangmp 1y ago> When thinking about backward compatibility... keep in mind that the old code was arguably already broken. There's not a good reason to keep letting it compile. Oh how I wish the C++ committee and compiler authors would adopt this way of thinking... Sadly we're dealing with an ecosystem where you have to curate your compiler options and also use clang-tidy to avoid even the simplest mistakes :/ Like its insane to me how Wconversion is not the default behavior.
- zahlman 1y ago>Oh how I wish the C++ committee and compiler authors would adopt this way of thinking Many different committees, organizations etc. could benefit, IMO.
- motorest 1y ago> Oh how I wish the C++ committee and compiler authors would adopt this way of thinking... I disagree. If you expect anyone to adopt your new standard revision, the very least you need to do is ensure their code won't break just by flipping s flag. You're talking about production software, many of which has decades worth of commit history, which you simply cannot spend time going through each and every single line of code of your >1M LoC codebase. That's the difference between managing production-grade infrastructure and hobbyist projects.
- markhahn 1y agoMost of that actually just makes sense if you approach it from the historic,low-level, minimalist direction. But maybe if you're coming from some other, higher-comfort language...
- frollogaston 1y agoComing from C, none of this made sense to me. Wut is `foo() = default;`? If you want a default value of 0, why isn't it just struct foo { int a = 0; }; In Python, which is higher-level ofc, I still have to do `foo = 0`, nice and clear.
- zabzonk 1y ago> If you want a default value of 0, why isn't it ... It is.
- frollogaston 1y agoI know that works too, but there are also other unclear ways to do it.
- Maxatar 1y ago`foo() = default;` is an explicit way to generate a default constructor for `foo`. The default constructor works by recursively calling the default constructors for all class instance fields. In C++ there are a bunch of rules about when a class has a default constructor or not, but by explicitly declaring one you are guaranteed to have it so long as all your class instance fields have default constructors. Your example of having a field called `a` that is initialized to 0 is perfectly valid C++ as well but it's not the same as an explicitly declared default constructor.
- AlienRobot 1y agoYeah, this is obviously nonsensical. If the constructor is "default" then why do you need to explicit set it? Yeah, I know some objects don't have constructors, but it would make more sense if you had to explicit delete the default constructor, or the keyword "trivial" was used instead of default.
- gitroom 1y ago[dead]
- nyarlathotep_ 1y agoAside, but the author of this blog is the author of https://nostarch.com/building-a-debugger https://nostarch.com/building-a-debugger A wonderful exploration of an underexplored topic--I've pre-ordered the hard copy and have been following along with the e-book in the interim.
- jeffbee 1y agoIt's fun to cross the streams of HN catnip. C++ sucks, it's too hard to use, the compiler should generate stores all over the place to preemptively initialize everything! Software is too bloated, if we optimized more we could use old hardware!
- Maxatar 1y agoI'm not familiar with programming languages that generate redundant stores in order to initialize anything. Usually what happens is the language requires you to initialize the variable before it's read for the first time, but this doesn't have to be at the point of declaration. Like in Java you can declare a variable, do other stuff, and then initialize it later... so long as you initialize it before reading from it. Note that in C++, reading from a variable before writing to it is undefined behavior, so it's not particularly clear what benefit you're getting from this.
- zahlman 1y ago> Note that in C++, reading from a variable before writing to it is undefined behavior, so it's not particularly clear what benefit you're getting from this. You gain the benefit that the compiler can assume the code path in question is impossible to reach, even if there's an obvious way to reach it. To my understanding, this can theoretically back-propagate all the way to `main()` and make the entire program a no-op.
- tialaramex 1y agoThere are real (in the sense they compile in actual C++ compilers people use and the resulting executable will run) toy (ie not intended for production use) examples where you can cause the compiler to conclude that executing this never-called function named format_hard_disk or begin_world_war_iii or whatever is the only remaining possible meaning of the program so that's what happens when it's run. Nobody has written an actual format-hard-disk or start-world-war-III routine, because we're not crazy, but the alarming function naming is intended to signal what a terrible idea it is to have this programming language at all.
- kazinator 1y ago> This rule makes sense when you think about it No, it is bonkers; stick to your consistent point, please. These two should have exactly the same effect: bar() = default; // inside class declaration bar::bar() = default; // outside class declaration The only difference between them should be analogous to the difference between an inline and non-inline function. For instance, it might be that the latter one is slower than the former, because the compiler doesn't know from the class declaration that the default constructor is actually not user-defined but default. How it would work is that a non-inline definition is emitted, which dutifully performs the initialization, and that definition is actually called. That's what non-bonkers might look like, in any case. I.e. both examples are rewritten by the compiler into bar() { __default_init; } bar::bar() { __default_init; } where __default_init is a fictitious place holder for the implementation's code generation strategy for doing that default initialization. It would behave the same way, other than being inlined in the one case and not in the other. Another way that it could be non-bonkers is if default were simply not allowed outside of the class declaration. bar::bar() default; // error, too late; class declared already! Something that has no hope of working right and is easily detectable by syntax alone should be diagnosed. If default only works right when it is present at class declaration time, then ban it elsewhere.
- alexvitkov 1y agoThis is not even worth thinking about, just type " = {}" on every struct/class member and every variable declaration, and forget about all this nonsense.
- dataflow 1y agoThat's a bad idea. It defeats tools (warnings, sanitizers, etc.) that try to tell you you have forgotten to place the semantically correct value in your variables. If you want indiscriminate initialization, a compiler flag is the way, not forcing it in the source code.
- adityamwagh 1y agoJust gonna leave this here :) https://youtu.be/7DTlWPgX6zs?si=-jNEIYQf1_uUioD- https://youtu.be/7DTlWPgX6zs?si=-jNEIYQf1_uUioD-
- ValtteriL 1y agoThe book Beautiful C++: 30 Core Guidelines for Writing Clean, Safe, and Fast Code recommends initializing/providing default values for member variables in default member initializers intead of the initializer list used here. """ Default member initializers define a default value at the point of declaration. If there is a member that cannot be defined in such a way, it suggests that there may be no legal mechanism by which a default constructor can be defined. """
- agent327 1y agoThe answer to this is to replace default-init by zero-init. This removes all special cases and all surprise, at a cost that is minimal (demonstrated experimentally by its implementation in things like Windows and Chrome) or even negative. Doing so would make software safer, and more reproducible, and it would make the object model more sound by removing the strange zombie state that exists only for primitive types. Of course we should provide a mechanism to allow large arrays to remain uninitialized, but this should be an explicit choice, rather than the default behaviour. However, will it happen? It's arguably the easiest thing C++ could do to make software safer, but there appears to be no interest in the committee to do anything with safety other than talk about it.
- monkeyelite 1y agoWe all agree, poor defaults were chosen in C++ across the board. we have learned a lot about languages since then. The question is what to do about it - balancing the cost of change to code and to engineers who learned it. > but there appears to be no interest in the committee to do anything with safety other than talk about it. There is plenty of interest in improving C++ safety. It’s a regular topic of discussion. Part of that discussion is how it will help actual code bases that exist. Should the committee do some breaking changes to make HN commenters happier, who don’t even use the language?
- agent327 1y agoI was not proposing sweeping changes to all the defaults in C++, I was proposing to adopt a single, specific change. That change does not break any existing code, removes pitfalls from the language, and has already been tried by industry and found to be beneficial. Why is it not in C++26? https://open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2754r0.html https://open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2754r0... provides what appears to be the answer to this question: "No tools will be able to detect existing logical errors since they will become indistinguishable from intentional zero initialization. The declarations int i; and int i = 0; would have precisely the same meaning." ...yes, they would. _That's the point_. The paper has it exactly the wrong way around: currently tools cannot distinguish between logical error and intentional deferred initialization, but having explicit syntax for the latter would make the intention clear 100% of the time. Leaving a landmine in the language just because it gives you more warnings is madness. The warning wouldn't be needed to begin with, if there were no landmine. I'm not sure what you mean with "who don't even use the language". Are you implying that only people that program professionally in C++ have any stake in reliable software?
- monkeyelite 1y agoInitialization does look insane. But as with most C++ complexity this is inherent. Lists of the “good parts” of C++ over C usually include RAII. But f we imagine starting with C and adding C++ features to see when complexity explodes. I think the worst offender is constructor/destructor. They require the language to perfectly track the lifetime of each member of every structure. If you resize a vector, every entry must call a constructor. If exceptions are possible, but insert little cleanup calls into all possible code paths. Want to make a copy of something? Who is responsible for calling constructor/destructor. Want to make a struct? What if one member requires construction? How do you handle a union? The result is micromanaging and turning most operations into O(n) init/cleanup calls. The modern C approach avoids all of this and allows you to manage pieces of memory - rather than values. Zero initialize or leave uninitialized. So what do we lose? Well classes own resources. If you have a vector<MyObject> and MyObject has a member vector<Items> then we should be able to cleanup without looking inside each member of each element. I think we should separate resource allocation from use. Allocators are the things that care about cleanup, move, etc. This should be the exception - rather than the default way to think about structs.
- gpderetta 1y ago> Want to make a copy of something? Who is responsible for calling constructor/destructor. What do you mean? The compiler will do it for you. > This should be the exception - rather than the default way to think about structs. the way that RAII in C++ recursively construct and destroys arbitrary object graphs is extremely powerful. It is something that very few other languages have (Rust, any other?). It should definitely be the default. > I think we should separate resource allocation from use. Allocators are the things that care about cleanup, move, etc. I'm not sure what you mean by use. If you mean we should separate allocation from construction, I agree! But then so does C++. They are tied by default, but it is easy to separate them if you need it.
- monkeyelite 1y ago> What do you mean? The compiler will do it for you. It will. And the only cost to you is you have to understand initializer lists, pod types, copy constructors, move constructors, launder, trivially_destructible, default initialization, uninitialized_storage, etc. > the way that RAII in C++ recursively construct and destroys arbitrary object graphs is extremely powerful And extremely complex. The big fallacy here is that you would want to manage resources at the individual node level - rather than for a batch. > I'm not sure what you mean by use It’s similar to the idea of arenas (also made difficult by constructors btw). You can make sophisticated systems for managing allocations of individual nodes in a graph - like reference counted smart pointers. Or you can avoid the problem entirely by deallocating the whole group at once. Imagine if C++ structs were required to be pod and classes were not. Then you could always know that a struct can be trivially allocated/deallocated etc. Then you could design data structures for pod types only that didn’t have to worry about O(n) cleanup and init.
- dschuetz 1y agoAre AI chatbots aware of this?
- bluGill 1y agoThis is obsolete and wrong! Variables are now - well as of C++26 which is next year - required to be initialized in C++. C++ is not standing still, it is getting better (well mostly better). You can do stupid things in C++, but I've seen stupid things in every non-toy language (and even most toys)
- rnikander 1y agoBeen trying Rust for some months now. The IDE experience is so much nicer I'm probably sticking with it, but the restrictions on how I write code are still grating on me. I need to learn the language better, but now I still feel like I wanted something more like C++ 2.0 without as much paradigm shift.