Re: My problem in C

@95
For motivation as to why C++ is better, look up std::shared_ptr, which prevents memory leaks through use of copy constructors and destructors to do reference counting without you having to make sure you put all the calls in the right place, and also helps you avoid dereferencing pointers after they're freed (since the pointer doesn't get freed until all shared_ptr that refer to it are unreachable).  C and C++ fill the same niche, but C++ allows you to write higher level abstractions.  For example, if you need a growable container of Foobars in C (basically a Python list), you have to use void* or do a bunch of really ugly preprocessor magic, but in C++ it's just a Vector<Foobar> and is fully typesafe and frees itself for you when objects die and etc.

The problem is, well, suffice it to say you're underestimating the scope of C++ by an order of magnitude.  C is maybe 10% as complicated as C++.  You can get the basics down quickly, but I've got around 20000 lines of C++ under my belt and I still have to look up language constructs from time to time.  People will choose C over C++ for this reason, and also because it is a bit easier in C to formally verify code is correct in code reviews and stuff, plus sometimes C++ has hidden costs when people i.e. decide to do something very expensive in their destructor.

but to use something like Synthizer as an example, base classes without the pointer to first field trick is a big one, as is not having to manually implement vtables, which are basically giant structs of function pointers that you have to manually fil out if you need a "object" that can be inherited from in C.  Templates combined with closures let me do functional programming stuff where I have a function that handles the loop that determines which objects are alive and which aren't without having to rewrite it every time.  So for example (and yes, this is valid, and yes, I know that it's esoteric syntax until you know how to read it, but it's also idiomatic):

weak_vector::iterate_removing(my_vector, [&](auto &x) {
    x.do_something_interesting()
});

Will walk over a vector of weak_ptr<Bla>, calling the provided closure on each, and both skip and remove dead ones.  Weak_ptr is a type that can refer to something in a shared_ptr, but which doesn't keep it alive, so when a Synthizer object dies the weak_ptr can safely tell us that, and there's an array deletion algorithm where you swap items around and shrink the lenght that works when the order doesn't matter instead of having to copy that's 30 lines, all of which just happen in iterate_removing, and suddenly I've replaced all the for loops with iterate_removing and a bit of esoteric syntax.  Plus it works on any vector<weak_ptr<bla>> where bla has a do_something_interesting method and you'll notice I don't even have to refer to the types involved since C++ can infer them for you, and C++14 (or maybe C++17) added the auto shorthand to make closures templates implicitly so you don't have to mention types there either, and can just use auto instead.  Also it's a closure so you can access variables in the enclosing scope--it's in all ways equivalent to the for loop with 30 lines in terms of capability, and even as fast after the compiler gets done with it.

You can't do this in C because the C equivalent is declare a second function somewhere outside the one you're in, make a struct that has all the things you want to reference from the thing that wants to use iterate_removing, copy things into it by hand, give a void* pointer to iterate_removing along with the function pointer, and cast it back.  Also your array isn't std::vector, it's some homegrown thing where you have to use void* everywhere and some sort of size and memcpy, or you have to write a giant macro where every line ends in \ since macros can only technically be one line that declares array types for you.  And you don't get the dot syntax so you can't chain methods.  And you don't get auto so if any of the types change you have to touch the whole codebase.  Etc.

But there is a pretty big downside.  That 3 line example involves something like 5 or 6 language concepts--that's more than one per line.  And if it's more complicated, well, the closure captures implicitly by reference, and though you can explicitly list the variables instead of using the capture everything implicitly by reference shorthand, by reference means pointers to the stack frame of the function creating the closure, so if the closure outlasts the creator then that stack frame is gone and it starts reading junk data.  And for as great as shared_ptr is, and not crashing is amazing, you replace "I dereferenced an invalid pointer" with reference cycles, which is what happens when a.ptr refers to b and b.ptr refers to a, where nothing can tell that a and b can die so they just sit around in memory unreachable forever (cycle detection when reference counting requires a garbage collector, which C++ doesn't have).  But also, replacing auto &x with auto x in that example is wrong because auto x is a copy, not a reference.  In the case of iterate_removing that just means a temporary extra reference, but it is still an order of magnitude slower even when it isn't going to crash your program.  And while you can learn that auto &x is "infer a type that's a reference to something", the actual explanation of what's going on takes  a huge page on cppreference.com that explains how auto is inferred by writing an imaginary function template and using the function template inference algorithm instead and the actual C++ specification explains this in LaTeX as math symbols.

Since that's a big paragraph, to break it down slightly, [&] means "this closure captures by reference", auto &x means "infer the type of this closure parameter", since even answering the question you asked is involving a lot of concepts.  Except that auto &x "inferring a type" is a lie.

I like it because I fall on the abstraction side of the spectrum, not the easy to understand language side of the spectrum.  It has made synthizer under half the size of what it would be if I had used C, while literally allowing me o implement abstractions that just aren't possible, for example:

context->call*([&] () {
// stuff
});

lets me just drop into the audio thread for a few lines anywhere, which is how i.e. object creation is done without races.  And shared_ptr and weak_ptr let me abstract the C handles by writing a dedicated C handle file that knows how to hold onto a shared_ptr as long as the C side has a handle, so that when the C side drops it all of the C++ stuff just handles eracing, which then invalidates the weak_ptr, which then feeds into iterate_removing, which knows how to get rid of all the dead references without just leaving them in all the vectors forever.  And I wrote something like 60 lines to get all of that.  Total, then another 5 or so got me:

context->createObject<Source>(args, to, constructor);

which sets all of this up.  About half of what I'm describing you just can't do in C.

But the moral is, with great power comes great responsibility.  I use C++ right, and in so doing I produce more reliable software in half the lines that it'd take in C and in much less time, usually much less than half the lines and half the time.  But the flip side is that it's also trivially easy to produce software that's so buggy you'll never fix it, in such a manner that every single file of the project requires knowing every single concept of C++, which as you'll see is really a lot.  You're probably getting that from just these examples.  but you don't necessarily know that you're in trouble right away.  For example deadlocks in destructors, which are implicitly called by the compiler at scope exit, or the fact that std::thread implicitly joins (which will block forever if you don't signal it to stop), or abuse of references which can change foo(x) from taking a struct by value to taking a reference just by changing the function signature, which is like saying "what if in C pointers just happened without any way to tell" and suddenly you explode because someone changed this without bothering to check, the compiler went "yep, you know what you're doing", and everything implicitly became a pointer and they got invalidated because they're on the stack.

If you want to move from C to a higher level systems language, I suggest Rust over C++.  Synthizer uses C++ because it needs access to things Rust doesn't have yet, namely and most critically const generics, but unless you're doing particular kinds of mathematical code you don't need const generics and Rust should have them in a year or so at most anyway.  Among many other things Rust has easy JSON and the templates aren't a hack, they're actually a proper type system that isn't literally copy/pasting done by your compiler (templates aren't literally that either--but they're close).  Plus you get Cargo, which is like Pip, and you don't have to learn CMake or something either.  Also hey, how about a proper module system.  Etc etc etc.  There is a reason I don't praise C++ even though I use it.



-- 
Audiogames-reflector mailing list
Audiogames-reflector@sabahattin-gucukoglu.com
https://sabahattin-gucukoglu.com/cgi-bin/mailman/listinfo/audiogames-reflector
  • ... AudioGames . net Forum — Developers room : Dark Eagle via Audiogames-reflector
    • ... AudioGames . net Forum — Developers room : camlorn via Audiogames-reflector
    • ... AudioGames . net Forum — Developers room : Dark Eagle via Audiogames-reflector
    • ... AudioGames . net Forum — Developers room : camlorn via Audiogames-reflector
    • ... AudioGames . net Forum — Developers room : Dark Eagle via Audiogames-reflector
    • ... AudioGames . net Forum — Developers room : camlorn via Audiogames-reflector
    • ... AudioGames . net Forum — Developers room : amerikranian via Audiogames-reflector
    • ... AudioGames . net Forum — Developers room : camlorn via Audiogames-reflector
    • ... AudioGames . net Forum — Developers room : Dark Eagle via Audiogames-reflector
    • ... AudioGames . net Forum — Developers room : camlorn via Audiogames-reflector
    • ... AudioGames . net Forum — Developers room : Dark Eagle via Audiogames-reflector
    • ... AudioGames . net Forum — Developers room : camlorn via Audiogames-reflector
    • ... AudioGames . net Forum — Developers room : Dark Eagle via Audiogames-reflector
    • ... AudioGames . net Forum — Developers room : camlorn via Audiogames-reflector
    • ... AudioGames . net Forum — Developers room : Dark Eagle via Audiogames-reflector
    • ... AudioGames . net Forum — Developers room : camlorn via Audiogames-reflector
    • ... AudioGames . net Forum — Developers room : Dark Eagle via Audiogames-reflector
    • ... AudioGames . net Forum — Developers room : camlorn via Audiogames-reflector

Reply via email to