Announcement

Collapse
No announcement yet.

Scope-Based Resource Management Infrastructure Merged For Linux 6.5

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • #31
    Originally posted by coder View Post
    LOL, wut?
    I already know real-world nightmare stories about people using, say, overlapped I/O with Windows APIs and a structure on the stack, then getting a silent exception they weren't prepared for, thrown by some component that looks harmless, which unwinds the stack. Then later the stack gets filled again, but the async I/O will write to the same location it was originally registered for (note that this can take a very long time in terms of CPU cycles, since I/O is slow). Have fun debugging this, can take weeks of hair pulling.

    Originally posted by coder View Post
    Most of the C++ standard library is pretty good. And standard containers & algorithms... I mean that's one of the killer apps for C++ over C!
    That's the part I hate the most, too verbose and makes it look like an entirely "different" kind of language, and with potential for massive bloat under-the-hood if you don't realize all the subtleties of the very-complex language rules, which is a big turnoff for many when it comes to C++.

    People who hate C will of course agree with you. I'm talking about from perspective of someone who chooses C over C++. There's not much reason to do so, if you choose a sane subset of C++, and by "sane" I mean from a C programmer's point of view. (which includes simplicity)

    Originally posted by coder View Post
    But libc's string handling functions generally suck, are hard to use properly, and promote insecure & fragile code. The C++ standard library added threading and filesystem abstractions that are also way better than libc & pthreads.
    I agree about string functions, but in general don't use strings? Even if you have to, you need them for some other API that probably accepts them, so you're forced to use C strings. And it's not hard to write your own basic string class.

    Comment


    • #32
      Originally posted by Weasel View Post
      I already know real-world nightmare stories about people using, say, overlapped I/O with Windows APIs and a structure on the stack, then getting a silent exception they weren't prepared for, thrown by some component that looks harmless, which unwinds the stack. Then later the stack gets filled again, but the async I/O will write to the same location it was originally registered for (note that this can take a very long time in terms of CPU cycles, since I/O is slow). Have fun debugging this, can take weeks of hair pulling.
      So, you're saying:
      1. They dispatched some async operation with a callback that used some state on the stack.
      2. Before they were able to block on the callback, some exception occurred in some intervening code.
      3. This exception unwound the stack, destroying the structure the async callback is going to use.
      4. Callback occurs, now seeing a garbled structure and/or trashing whatever is in its place.

      In this case, there are two sensible solutions:
      1. Bind the async operation to the scope of the structure. If an exception (or early return) happens, the async operation should get cancelled. If you can't reliably cancel the operation (as is the case with some APIs), then you'll need some way of telling the callback to prematurely exit, before it looks at that structure.
      2. Don't use stack-based datastructures for your async callback. Used heap, instead. Boost.Asio recommends people use shared_ptr<> for async operations, precisely to avoid the state going out of scope while it's still referenced.

      As mentioned above, this same condition can occur with C, if you have an early-return instead of stack unwinding via exception. setjmp()/longjmp() pose a similar hazard, here.


      Originally posted by Weasel View Post
      That's the part I hate the most, too verbose and makes it look like an entirely "different" kind of language,
      Well, there's not much getting around template syntax, but:
      • C++11 introduced the auto keyword, saving a lot of verbosity when dealing with iterators or insertion-results.
      • C++11 introduced the for-each loop, again avoiding lots of iterator verbosity.
      • C++20(?) enables class template argument deduction, so you can simply say: std::vector v = { 1, 2, 3 };

      Originally posted by Weasel View Post
      and with potential for massive bloat under-the-hood
      The linker does a pretty good job of handling the bloat issue. Honestly, bloat is something people used to worry about with C++ like 20+ years ago. When I look at the size of stripped executables or libraries, there really doesn't seem to be a problem with bloat.

      Originally posted by Weasel View Post
      if you don't realize all the subtleties of the very-complex language rules, which is a big turnoff for many when it comes to C++.
      Sure, it's a complex language. That's a different argument, though. I thought we were just talking about the standard library.

      Originally posted by Weasel View Post
      Even if you have to, you need them for some other API that probably accepts them, so you're forced to use C strings.
      std::string has an underlying representation of a null-terminated (i.e. C-compatible) string. There's an easy way to access it (c_str()) and good support for interoperability with C strings.

      Originally posted by Weasel View Post
      And it's not hard to write your own basic string class.
      That would be a waste of time and more opportunities for bugs. Also, std::string is pretty well-optimized, having features like local-storage for short strings.

      Reimplementing your own versions of standard library classes and functions is like so 1990's. Not only is your version likely to be worse (and probably buggy), but it also increases the learning curve for anyone joining your project and creates another hurdle for you to utilize 3rd party C++ libraries.

      The only time it would make much sense is in specialized environments like in-kernel usage or maybe use on microcontrollers or other places where you don't have a full libc underneath.
      Last edited by coder; 07 July 2023, 10:55 AM.

      Comment


      • #33
        Originally posted by coder View Post
        So, you're saying:
        1. They dispatched some async operation with a callback that used some state on the stack.
        2. Before they were able to block on the callback, some exception occurred in some intervening code.
        3. This exception unwound the stack, destroying the structure the async callback is going to use.
        4. Callback occurs, now seeing a garbled structure and/or trashing whatever is in its place.

        In this case, there are two sensible solutions:
        1. Bind the async operation to the scope of the structure. If an exception (or early return) happens, the async operation should get cancelled. If you can't reliably cancel the operation (as is the case with some APIs), then you'll need some way of telling the callback to prematurely exit, before it looks at that structure.
        2. Don't use stack-based datastructures for your async callback. Used heap, instead. Boost.Asio recommends people use shared_ptr<> for async operations, precisely to avoid the state going out of scope while it's still referenced.

        As mentioned above, this same condition can occur with C, if you have an early-return instead of stack unwinding via exception. setjmp()/longjmp() pose a similar hazard, here.
        I never said that it can't be solved. It obviously did get solved. Simplest is to catch the exception and cancel it manually. Harder is to encapsulate the Win32 API into some object with destructor that cancels it.

        The problem is that this was NOT OBVIOUS at all, which is why it's a nightmare to debug.

        Exceptions violate the basic control flow simplicity. Functions can now "return" from any place whatsoever. It's hilarious how it makes code extremely hard to reason about or review.

        Originally posted by coder View Post
        The linker does a pretty good job of handling the bloat issue. Honestly, bloat is something people used to worry about with C++ like 20+ years ago. When I look at the size of stripped executables or libraries, there really doesn't seem to be a problem with bloat.

        Sure, it's a complex language. That's a different argument, though. I thought we were just talking about the standard library.
        I didn't mean just code size bloat, although that's a factor as well, but also bloat in terms of "features" that you have to remember to actually use the damn library "well". And even then it's bloated in syntax, because everything is verbose, you have to use scopes everywhere, etc.

        For example type traits are super useful in meta-programming, but the default ones are overly verbose.

        Here's a simple example: std::is_null_pointer<type>::value. WTF? I know there's an alias now is_null_pointer_v, which is STILL too verbose, but the point remains who in their right mind considered the original one "reasonable" to actually use in code.

        So I defined my own type traits in my own "lite" utility library, since I compile without stdlib. For null pointers, I simply use is_null<type> (only pointers can be "null" so saying pointer is useless information). Now you see how concise it is? No std:: prefix either.

        In code it looks manageable now.

        Originally posted by coder View Post
        Reimplementing your own versions of standard library classes and functions is like so 1990's. Not only is your version likely to be worse (and probably buggy), but it also increases the learning curve for anyone joining your project and creates another hurdle for you to utilize 3rd party C++ libraries.
        We're talking about people who hate C++ because it's already too complex to learn and the standard library is too bloated (see above).

        I don't care about people who are used to the C++ standard library in my arguments. We're literally talking about using a subset of C++ for people who hate the freaking language, and I'm justifying why it's a solution and C++ doesn't really suck, it's the people who use it that do.

        We literally target different audiences. "Veteran" C++ devs are the reason the language sucks for those who hate it. This audience likes the simplicity and short syntax of C library functions, and how small it is (in terms of having to learn it).

        Comment


        • #34
          Originally posted by Weasel View Post
          I never said that it can't be solved. It obviously did get solved. Simplest is to catch the exception and cancel it manually. Harder is to encapsulate the Win32 API into some object with destructor that cancels it.
          Async programming is generally hard. It's that much more important to follow establish patterns and styles, to avoid these sorts of pitfalls.

          The RAII solution (#1 in my post) is catered for either by BOOST_SCOPE_EXIT​() or (as kreijack mentioned) the upcoming std::scope_exit(). That's really the best option, if you don't have a full-fledged C++ wrapper like Boost.Asio.

          Using a properly-designed C++ wrapper is that much more important, for such a hazard-prone area like async programming.

          Originally posted by Weasel View Post
          The problem is that this was NOT OBVIOUS at all, which is why it's a nightmare to debug.
          A lot of problems are like that, when you first encounter them. Try watching a new C programmer struggle with the distinctions between stack and heap memory. They probably have a similar reaction to something that's almost second-nature to us.

          RAII is the cleanest and most reliable approach to writing exception-safe code. If someone is using exceptions without understanding issues around exception-safety, that borders on professional malpractice, IMO.

          Originally posted by Weasel View Post
          Exceptions violate the basic control flow simplicity. Functions can now "return" from any place whatsoever. It's hilarious how it makes code extremely hard to reason about or review.​
          goto and setjmp()/longjmp() can also be really tricky. signal() can interrupt threads that seem uninterruptible. Control flow never was exactly trivial.

          When I write code, I basically have a fully-defensive mindset. For each function I call, I'm always thinking about how it can fail and what I need to do to handle that. Exceptions make this all the more important, because their power comes at cost. IMO, it's a tradeoff worth making, but you have to be aware of that tradeoff and what it means for you.

          Originally posted by Weasel View Post
          I didn't mean just code size bloat, although that's a factor as well, but also bloat in terms of "features" that you have to remember to actually use the damn library "well". And even then it's bloated in syntax, because everything is verbose, you have to use scopes everywhere, etc.
          With the C++11 features I mentioned, the verbosity problem has become virtually a non-issue. Regarding namespace and class-scoped names, you have to keep in mind the C equivalent. Going back to some of my OpenGL notes... let's talk about this friggin' gem:

          That's 45 characters (excluding parenthesis). At least, in C++, I don't have to specify a scope qualifier if I'm either inside the scope or have a using directive for a namespace in the current scope. Furthermore, you can make namespace and type aliases, which let you use much shorter names when one of those prior conditions isn't true.

          In C, you just have to suck it up and make long-ass and/or inscrutable function names, if you want to avoid collisions or give callers a chance of figuring out what a function does.

          Originally posted by Weasel View Post
          For example type traits are super useful in meta-programming, but the default ones are overly verbose.

          Here's a simple example: std::is_null_pointer<type>::value. WTF? I know there's an alias now is_null_pointer_v, which is STILL too verbose, but the point remains who in their right mind considered the original one "reasonable" to actually use in code.
          Most C++ programmers and most C++ code doesn't do metaprogramming. That's a cherry-picked example that's not representative of the overall usage experience.

          Regarding is_null_pointer_v<>, C++11 didn't have a key language feature needed to do that. It's called a "variable template", and didn't come about until C++14. But, they still needed a way to do overloads and SFINAE on the new nullptr_t, so they couldn't just wait until C++14 to add that trait.

          Originally posted by Weasel View Post
          So I defined my own type traits in my own "lite" utility library, since I compile without stdlib. For null pointers, I simply use is_null<type> (only pointers can be "null" so saying pointer is useless information). Now you see how concise it is? No std:: prefix either.
          1. The name "null" exists in other contexts. I guess they wanted to avoid confusion or collisions, which seems like reasonable thing for a standard library to do: https://en.wikipedia.org/wiki/Null-terminated_string
          2. Namespaces are important for minimizing the chance of type name & symbol collisions. In your own programs and private libraries, you can afford not to use them, but a public & widely-used library cannot. Indeed, it's precisely because libraries are well-mannered in their use of namespaces that developers can afford not to use them!

          Originally posted by Weasel View Post
          We're talking about people who hate C++ because it's already too complex to learn and the standard library is too bloated (see above).
          Eh, this seems to be on track to degenerate into a freewheeling C++ rant-fest. I get where I opened the door to talking about exceptions, and then you also made a claim about not using the C++ standard library, to which I responded. Beyond that, you're starting to lose me.

          I'm not really interested in the broader topic of C++ as a language, because it's an endless and ultimately pointless exercise. It is what it is, so if you find it too complex then don't use it. But, if you want to talk about how to use specific aspects of it, then I'm willing to share my experience if I can.

          Originally posted by Weasel View Post
          This audience likes the simplicity and short syntax of C library functions, and how small it is (in terms of having to learn it).
          There's a classic tradeoff, in programming languages. You can put most of a program's complexity in the language and runtime libraries, or you can have it in your own code. The benefit of putting it in the language and libraries is that your own code is simpler and smaller. Also, we expect the compilers and libraries to be much better-tested and scrutinized than the typical individual's code.

          The downside is that using them becomes more complex. I'm not going to claim that C++ got everything right, but it offers the potential of close-to-the-metal performance along with powerful compile-time and runtime abstraction mechanisms. Using a complex tool without a good understanding of it will hurt you. People can choose to invest a little time up-front and a lot on the back-end, but C++ at least gives one the option of investing more time up-front and reaping significant dividends down the line.
          Last edited by coder; 09 July 2023, 04:34 PM.

          Comment


          • #35
            For the record, I'm talking about people who hate C++ while they love C. I'm not one of them, although I do generally hate most of the standard library, but I do use it daily (and am required to for work). I can 100% understand people who hate it, though. Hence the argument.

            I do believe a proper subset of C++ would be objectively better than C, but people have a hard time accepting this subset since they'll want to cater to "people used to C++". I'd rather they consider it a different language, say, C+ and get rid of this mindset, and then they'll see much of C++ is indeed better than C, even if they're used to C's succint and simple ways.

            C++ started to deviate from C a long time ago. What I propose is to go back to the "extend C" mindset, with very useful features, instead of catering to the programmers who hate C in the first place; I hate that audience.

            Anyway, you're right that goto or longjmp is also a problem, but those are usually frown upon in code unless really needed, and even those are more obvious than invisible exception "returns". I like goto, but when it makes code cleaner (e.g. error handling to common exit block, escaping out of nested loops, and so on), not to return spaghetti control flow. I don't think it's anywhere close to "any line of code can potentially throw an exception" thing.

            Comment


            • #36
              Originally posted by Weasel View Post
              I do use it daily (and am required to for work). I can 100% understand people who hate it, though. Hence the argument.
              For someone who uses it daily, have you not heard about the using directive or auto keyword? If you put some time into learning the tools you're using, they'll work better for you. If it's too big an ordeal to work your way through The C++ Programming Language (which I did with the shorter 3rd edition), Stroustrup wrote a much lighter "refresher" manual: A Tour of C++. You might actually find one of the previous editions more useful, depending on which iteration of the C++ language standard you're using.

              When picking up a major new language or framework, just about the first thing I do is RTFM. People have gotten so lazy about such things, these days. You cannot replace a proper understanding with some sporadic web searches and Stack Overflow.

              At the bare minimum, you really should spend some time reading through the C++ Core Guidelines. I don't agree with them 100%, but they're a great starting point and you'd better know you stuff before deciding not to follow them.

              Originally posted by Weasel View Post
              Anyway, you're right that goto or longjmp is also a problem, but those are usually frown upon in code unless really needed,
              I happen to think goto can be used effectively for error handling, in C. If you're careful, you can use it to roughly mirror the cleanup-at-scope-exit behavior of C++. However, if you're using cleanup attributes & functions, that's an even better option.

              Originally posted by Weasel View Post
              even those are more obvious than invisible exception "returns".
              Not setjmp()/longjmp()! However, I've only used them in one case, and that's where simply returning wasn't an option.
              Last edited by coder; 09 July 2023, 04:52 PM.

              Comment


              • #37
                Originally posted by coder View Post
                For someone who uses it daily, have you not heard about the using directive or auto keyword? If you put some time into learning the tools you're using, they'll work better for you. If it's too big an ordeal to work your way through The C++ Programming Language (which I did with the shorter 3rd edition), Stroustrup wrote a much lighter "refresher" manual: A Tour of C++. You might actually find one of the previous editions more useful, depending on which iteration of the C++ language standard you're using.

                When picking up a major new language or framework, just about the first thing I do is RTFM. People have gotten so lazy about such things, these days. You cannot replace a proper understanding with some sporadic web searches and Stack Overflow.

                At the bare minimum, you really should spend some time reading through the C++ Core Guidelines. I don't agree with them 100%, but they're a great starting point and you'd better know you stuff before deciding not to follow them.
                I guess I wasn't clear. I love namespaces and I think they're a feature C should have as well. But "proper C++ guidelines" for most projects I seen are to always specify the stupid prefix and not have "using" directives. My issue was more about people using C++ than the language itself, but I already did say that. They also dislike "nested" namespaces, which I find idiotic as well, since "using" directive exists. You don't even have to import them on the preceeding scope, you can just define a namespace alias.

                constexpr is another feature I wish C would have, so are "basic" classes (no inheritance, I hate inheritance).

                Mind you, I'm pretty deep versed into the subtleties of C++ (not really a language lawyer though), since I did a lot of hobbyist meta-programming as well. That's partly why I have such strong opinions about it, lol. I dislike how the standard library is designed, though.

                But probably my worst hatred is for exceptions, I'm sure that's known now.
                Last edited by Weasel; 10 July 2023, 09:25 AM.

                Comment


                • #38
                  Originally posted by Weasel View Post
                  "proper C++ guidelines" for most projects I seen are to always specify the stupid prefix and not have "using" directives.
                  Only at file scope, in public header files. Inside your .cpp file, I've not heard a good argument not to do it at file scope, although I usually don't bother for the std namespace. In either case, you can always do it within a function.

                  Originally posted by Weasel View Post
                  They also dislike "nested" namespaces, which I find idiotic as well, since "using" directive exists.
                  Nested namespaces are great. In my project we use a top-level namespace. Let's call it product. All of our libraries use this as their top-level namespace, so none of them actually need to specify it.

                  Then, each library typically has its own inner namespace. So, you might have a nework library, which then has maybe some classes like Stream, Server, etc. The nested namespaces allow us to use very simple and concise class and function names, whereas you might otherwise have to call them NetworkStream, NetworkServer, etc. Inside of the network namespace I don't have to say network::Stream, I can just say Stream. Same, when I'm using namespace network (which should only be in contexts where it's obvious or the scope is small enough that you won't miss the using directive).

                  Inside another class using network::Stream, I might have a type alias like:

                  using stream_type = network::Stream;


                  ...if I think there's some value in having that abstraction.

                  Originally posted by Weasel View Post
                  constexpr is another feature I wish C would have,
                  Before C++11, we got by with integral constant expressions. For most purposes, a static const int is just as good as constexpr int.

                  Originally posted by Weasel View Post
                  so are "basic" classes (no inheritance, I hate inheritance).
                  What about operator overloading? C supports struct-assignment, which could be a problem if your struct has non-copyable or nontrivially-copyable members.

                  Also, overloaded operators, constructors, and destructors break C's property of "no hidden function calls". That might be why they've held the line where they did.

                  BTW, people hack inheritance into C, by making the "baseclass" the first member of a struct. You can then do pointer casts to walk up & down the hierarchy. It only works with single-inheritance, though.

                  Originally posted by Weasel View Post
                  I dislike how the standard library is designed, though.
                  What about smart pointers, threading, atomics, or the filesystem API?

                  Originally posted by Weasel View Post
                  But probably my worst hatred is for exceptions, I'm sure that's known now.
                  Exceptions aren't bad unless you fight them or just don't know what the heck you're doing.

                  Some basic rules to live by are:
                  • Document which recoverable exceptions each function or subset of your API throws.
                  • Write exception-safe code by using RAII.
                  • Catch only the exceptions you know are recoverable. Trying to handle exceptions without knowing whether doing so will leave the program in a bad state is a recipe for trouble.
                  • Don't catch-and-rethrow if you can avoid it. This is mostly for the benefit of having complete backtraces to the throw-site, but it also avoids cluttering up the code with lots of try/catch blocks.

                  Most of the trouble with exceptions is people not taking the time to understand them or how they fit into the language, before using or dealing with them.
                  Last edited by coder; 10 July 2023, 12:51 PM.

                  Comment


                  • #39
                    Originally posted by coder View Post
                    Nested namespaces are great. In my project we use a top-level namespace. Let's call it product. All of our libraries use this as their top-level namespace, so none of them actually need to specify it.

                    Then, each library typically has its own inner namespace. So, you might have a nework library, which then has maybe some classes like Stream, Server, etc. The nested namespaces allow us to use very simple and concise class and function names, whereas you might otherwise have to call them NetworkStream, NetworkServer, etc. Inside of the network namespace I don't have to say network::Stream, I can just say Stream. Same, when I'm using namespace network (which should only be in contexts where it's obvious or the scope is small enough that you won't miss the using directive).

                    Inside another class using network::Stream, I might have a type alias like:

                    using stream_type = network::Stream;




                    ...if I think there's some value in having that abstraction.
                    That's cool. I generally tend to use something similar, when it's me calling the shots...

                    Originally posted by coder View Post
                    Before C++11, we got by with integral constant expressions. For most purposes, a static const int is just as good as constexpr int.
                    Sorry, I wasn't clear, I meant constexpr evaluations (and functions).

                    Originally posted by coder View Post
                    What about operator overloading? C supports struct-assignment, which could be a problem if your struct has non-copyable or nontrivially-copyable members.
                    Operator overloading is great, but far too often people overuse it (well, it's not a language problem). Even the standard library does (see stream operator overloads for shifts...).

                    Originally posted by coder View Post
                    Also, overloaded operators, constructors, and destructors break C's property of "no hidden function calls". That might be why they've held the line where they did.
                    True, but that's only if it's abused though. If it's used sensibly, it's not an issue for me (as long as you don't use + to do something not related to addition). I can imagine, for example, a bitfield class or something else working on bits, where you'd want to overload operators like this.

                    I'm also a fan of properties, even though they "hide function calls" even further, as long as they are used sensibly (e.g. invalidating a cache on a setter, but otherwise functioning like a setter). Too bad C++ doesn't have them natively, though now we can do them with zero cost using [[no_unique_address]], although it requires some hacks computing offsets to do that, but it only looks bad on the implementation, the user of the properties is "clean".

                    Originally posted by coder View Post
                    BTW, people hack inheritance into C, by making the "baseclass" the first member of a struct. You can then do pointer casts to walk up & down the hierarchy. It only works with single-inheritance, though.
                    I don't mind "manual" inheritance, but making inheritance "easily" accessible tends to be abused by people once again, and multiple inheritance is pretty much a nightmare (and also inefficient). If you do manual inheritance, most of the time you find out that composition is better, for example.

                    Even runtime dispatches you can write yourself with your own vtables, and not have to worry about "breaking the standard" (even though most compilers will lay out the vtbl in the same order of declaration, afaik that's not specified in the standard).

                    Originally posted by coder View Post
                    What about smart pointers, threading, atomics, or the filesystem API?
                    Smart pointers are nice, but somewhat trivial to implement yourself so not really an issue, IMO. The rest you can do with other APIs though, I usually just use the host API, so I can't really say there.

                    (well atomics you can't, but you can do with builtins or inline assembly wrappers, obviously).
                    Last edited by Weasel; 11 July 2023, 10:12 AM.

                    Comment


                    • #40
                      Originally posted by Weasel View Post
                      The rest you can do with other APIs though, I usually just use the host API, so I can't really say there.
                      Having used pthreads, Win API's threading, and boost/std threads, I'd say the latter wins hands-down. I also have very few complaints about how the std library wrapped pthreads.

                      As for filesystem, it's a huge win for cross-platform tools, IMO. I thought the path class was nicely done, although you'll probably take issue with how they overloaded operator/ to do portable composition/concatenation. I think it's nice, but then I also like using operator+ for string concatenation.

                      Comment

                      Working...
                      X