3 ms·
I was positively surprised by the constructiveness of the article itself. You'd usually get Linus' style rant about how C++ sucks so bad and C is the epitome of
by unscaled 11y ago
I was positively surprised by the constructiveness of the article itself. You'd usually get Linus' style rant about how C++ sucks so bad and C is the epitome of simplicity and design. I wholeheartedly agree with you that both languages are lacking, and I'm also waiting until Rust or another language grows mature enough to replace both of them in most cases.
But I think most of the main points you describe as impossible in C++ are actually completely possible. It means moving away from the 90's paradigm of using C++ to implement deep class hierarchies with design pattern, but after all C++ is a multi-paradigm language. If you can do data-oriented design in C, you could most certainly do it in C++, and the abstractions C++ provides actually make it easier.
In essence, I would separate game objects to a logic instance and state objects (pure structs) and use smart pointers with a generation-counter to point the logic instance, which in turn would have smart a pointer to the state struct. The smart pointer would overload the dereference operator and transparently update the logic instance to get the new vtable if needed.
This decoupling of state and logic could do many nice things, such as serializing the entire game state in a very clean way, and pure state structs would not be harder to parse than C structs (they would essentially be C structs), so you can still have your memory editor.
The main difference between C and C++ here would be the cost abstractions, both cognitive and performance-wise. I have to admit I've never ran into standard abstractions significantly slowing optimized debug code, except for standard library containers. I'm not a game programmer though, so YMMV. I get the cognitive cost argument, but for me the cognitive cost of C (namely having boilerplate noise scattered all over hiding the interesting code and having to be super-extra-careful with memory management) is higher than than the cognitive cost of internalizing all the layers of abstraction in C++.
If you're programming a network game, I still think you'd do your users a better service if you don't dismiss safety offhand. There's a whole class of memory-safety bugs which would never surface during normal play, but could still be exploited with specially crafted packets. Of course, C++ wouldn't give you perfect protection either.
- mikekchar 11y agoI agree with your depiction of C++. In fact, one of the benefits of C++ is that it is convenient to use stack allocation rather than heap allocation. This can really simplify memory management. The trick is that you have to choose a different idiom for your idiomatic C++ ;-). The trick is to always know who owns the memory and to never allocate memory in libraries. You use dependency injection, always pass by reference and always clean up in your destructors. If you are forced to allocate something in a library, you build a wrapper to deallocate it (or sometimes copy it when you receive the memory so that you can own it). It takes some experience to build applications this way, but it is well worth building that experience. C++ is still my language of choice for anything that requires fine control of memory and attention to performance. Rust looks like a very possible successor, but I agree that the lack of incremental compiling makes it not useful for large projects at the moment.
- loup-vaillant 11y ago> one of the benefits of C++ is that it is convenient to use stack allocation rather than heap allocation. Not quite. With its destructors (and the RAII that come with them), C++ makes it easy to follow a scoped discipline. Allocations are easily scoped, but they don't necessarily happen on the stack. Strings and vectors for instance, are typically allocated on the heap, assuming you're using the default allocator (which most people do anyway). While this is convenient and make things simpler, that's quite the runtime overhead, and causes latency problems similar to those of a genuine garbage collector (malloc() and free() aren't exactly constant time). > never allocate memory in libraries Now that can reduce the number of allocations in a program. Note that this also rules out most of the STL: its containers call their (possibly user defined) allocator themselves.
- mikekchar 11y agoIt is quite possible that I'm out of date (haven't used C++ in anger for the better part of a decade), but local variable allocations have always historically been on the stack. Just don't use new. Arrays will be allocated on the stack as long as they are fixed length. And yes, this rules out the STL :-(
- loup-vaillant 11y ago> local variable allocations have always historically been on the stack And they still are, thank goodness. My point was that as soon as you call a constructor, all bets are off: for instance, merely declaring an std::string allocates on the heap, local variable or no. But it seems you already knew that. I think it is important to distinguish scoped discipline from stack allocation. The former is a way to program. The later is an implementation detail (modulo performance). Your wording was a tiny bit sloppy, so I jumped at it.
- pcwalton 11y ago> I agree that the lack of incremental compiling makes it not useful for large projects at the moment. Whoa there. Let's not exaggerate the scope of the problem. Large projects in Rust usually consist of many crates (in Servo, 150+). Rust absolutely has incremental compilation on the level of the crate, so the compilation times are similar to what you see in C++ with per-directory unity builds. This is the same story as Go, for example, and people don't say Go isn't suitable for large projects; incremental compilation isn't even on Go's roadmap.
- crafn 11y agoGood that I succeeded at avoiding ranting. I find it really hard to keep from throwing absolutes. Almost everything is possible with C++, that's true. Some things require a lot of engineering though. Like full program reflection, fast debug builds, and fast (below 5s) builds. Funny that you mention the decoupling of state and logic, because I see that as a non-issue in C. State is a struct, logic is a function. There would need to be a really convincing argument to make me wrap the hundreds of game object components I'll have to two objects each. C has some differing cognitive cost, which C++ doesn't have, that's for sure. I think we have just differing personal preferences on which is worse :P (There are real-world situations where I'd choose C++ though) The network point is valid. Implementing it was mostly a nice learning opportunity. I haven't yet decided if I want to keep it or not. It currently lacks safety and proper compression, so if I get serious about it I'll probably make all network data go through validation functions, which helps with both compression and handling hostile data.
- unscaled 11y agoYou've said most of your build time was linking, so wouldn't you get the same build times with C? Templates would slow your build time a lot though, so they come with a price. C++ would never beat C on build times, but C never came close to Pascal, back in the day, and I'm pretty sure C# still beats the guts out of C with hands tied. :) > Funny that you mention the decoupling of state and logic, because I see that as a non-issue in C. I beg to differ. Decoupling logic and raw data is not an issue in C, but state is not the same as raw data. You may have the same state data duplicated in memory (e.g. an index), and there's also the issue of representation of state which is not portable (e.g. pointers, particular data structure). You also have pointers to functions (logic) in your data if I understood you right. With C++ (or any sufficiently high level language) you can easily abstract away the difference between state and raw data. If you've got function pointers or pointers to instances of logic objects (which is a must if you don't want to litter your code with long switch clauses), you could save them as type IDs or something akin to that. You might be programming something very different, but so far most good C code I've seen was thoroughly objected-oriented. Not in the sense it religiously followed the 3 pillars of OOP, but in the sense it coupled structs with several functions that have been meant to use on solely with these structs and sometimes employed polymorphism by embedding function pointers in these structs. All of that is much easier to do with C++ classes, and if this is all you wanted (and you don't think you need proper separation of data from bone fide state), you can have a code that would refresh the vtable pointer in memory, and would sure be more straightforward than manually updating function pointers all over the place. > It currently lacks safety and proper compression, so if I get serious about it I'll probably make all network data go through validation functions, which helps with both compression and handling hostile data. Validation is nice, but you can't foresee every weird way your data could be shaped. It's very easy to miss one small corner. That's why you see several security engineers going around these threads dissing C. :)