Dark Corners and Pitfalls of C++

(codeproject.com)

35 points | by SimbirSoft 1709 days ago

7 comments

  • jupp0r 1709 days ago
    A lot of things pointed out in the article shouldn't prevent anybody from starting a project in modern C++. First of all, usage of raw arrays is discouraged, if you use std::array or std::vector, there are way less ways to shoot yourself. Same goes for manually managing memory, you shouldn't ever have to call delete on your own nowadays. Most other problems mentioned in the article are ABI compatibility and dynamic library loading problems that are super specific to Windows and that very few people should ever have to deal with. This is going to painful no matter what language you are using. Win32 API is no fun, hardly C++s fault.
    • serial_dev 1709 days ago
      I see these kinds of comments very often ("Ackchyually, feature X of C++ has been discouraged for at least Y years now and if you still use it, you are doomed").

      I used to learn C++ at the university (~5 years ago) and have read multiple books on that, however, I would have no idea how to start using the language again and how to figure out which patterns are OK and which ones are discouraged and what should I use instead. All "modern C++" books I found are 10+ years old.

      There seem to be so many opinions out there on forums, but I don't see a comprehensive, community-approved place (book or website) where I can learn and understand the differences between the many ways of doing the same thing.

      • AlexeyBrin 1709 days ago
        > I don't see a comprehensive, community-approved place (book or website) where I can learn and understand the differences between the many ways of doing the same thing

        Check C++ Core Guidelines https://github.com/isocpp/CppCoreGuidelines/blob/master/CppC... which is a community approved collection of best practices for modern C++.

      • wvenable 1709 days ago
        > however, I would have no idea how to start using the language again and how to figure out which patterns are OK and which ones are discouraged and what should I use instead.

        I jump into C++ every 5 years or so and I haven't found this to be that big of a problem. Reading a few websites got me up to speed really quick.

        Having worked mostly in modern managed languages lately, I just look for the C++ solution that is as close to that as I can and it's now very easy to find that. My C++ code looks very much like my C# code.

        My own state of mind when working with C++ is that it feels good then do it, if it feels bad then go out and see if there is a better way.

    • praptak 1709 days ago
      I spent the last 6 years writing C++. Would not recommend for a new project, unless some very specific conditions are met. The main reason being the memory (un-)safety and the costs it generates.
      • einpoklum 1709 days ago
        I wonder what kind of C++ you've been writing.

        Memory safety is much less of an issue than it used to be. When you use spans over raw arrays (or vectors etc.) - you can have bounds checking; if you use std::array - you won't make invalid accesses. As for memory management - that's essentially a solved problem, you just use smart pointers, and generally not allocate yourself.

        It's true that you _can_ shoot yourself in the foot with C++; but it's becoming increasingly more difficult to do so unintentionally.

        • moosingin3space 1709 days ago
          > Memory safety is much less of an issue than it used to be.

          This is demonstrably false: https://twitter.com/LazyFishBarrel/status/112900096574140416...

          Additionally, new "safe" C++ APIs aren't: https://bugs.llvm.org/show_bug.cgi?id=34729

          Apple's iOS 12.4 release fixes 37 CVEs, 28 of which are memory unsafety (75.7%).

          Microsoft's statistics corroborate this: https://www.zdnet.com/article/microsoft-70-percent-of-all-se...

          Importantly, none of these sources show any improvements over time. If we saw meaningful improvements in "modern C++" codebases, I'd be much more willing to accept "modern C++". As it stands, there is no such evidence that "modern C++" provides any demonstrable benefits regarding memory safety.

          • jupp0r 1709 days ago
            > Importantly, none of these sources show any improvements over time. If we saw meaningful improvements in "modern C++" codebases, I'd be much more willing to accept "modern C++". As it stands, there is no such evidence that "modern C++" provides any demonstrable benefits regarding memory safety.

            I don't know of any study comparing memory safety of "modern C++" vs "legacy C++" code. None of the references you provided contains any information about that, so I think it's unfair for you to pretend that it does. From my work over the last years I can tell you that the move from C++98 to C++11 and to a smaller extend the move from C++11 to C++14 has reduced memory corruption bugs by a significant amount. The way we teach C++ to junior engineers is very different to what it was before, a lot of practices that used to lead to unsafe conditions are frowned upon nowadays. It's by no means perfect compared to Rust, but it's definitely not the mess that it's often characterized at when people point to code that would never pass code review nowadays (ie the original article).

          • kllrnohj 1709 days ago
            > Additionally, new "safe" C++ APIs aren't: https://bugs.llvm.org/show_bug.cgi?id=34729

            std::string_view is a borrow interface, not a safe owner. It is a sharp knife, to be sure, but it's not billed as being safe, either, and it's not broken.

          • einpoklum 1708 days ago
            The tweet you linked to does not demonstrate your claim; neither does the ZDNet article.
            • moosingin3space 1708 days ago
              All of these sources show memory safety problems getting worse or staying equally prevalent, which directly contradicts the parent's claim that memory safety is "much less of an issue than it used to be".
        • praptak 1709 days ago
          vector::operator[] does not check bounds, neither do some libraries that I have to use, notably Google Protocol Buffers can turn a logic bug (bad index to repeated field) into an access to unallocated memory.

          Best practices do not fully eliminate segfaults. This is a practical observation.

          • AlexeyBrin 1709 days ago
            > vector::operator[] does not check bounds

            True, but modern C++ encourages the use of range based for loops and std::algorithm functions which are safer to use.

            Also, if you want to check for bounds use std::vector::at which checks for bounds at runtime.

          • cameldrv 1709 days ago
            It's truly insane that after all of the water under the bridge in the past twenty years with buffer overflows that the default way to access an array in C++ is not bounds-checked. If there is a performance critical loop and you need to avoid the check, there should be a way to do it but for gods sake don't make that the default.
            • praptak 1709 days ago
              Wouldn't that be against C++ philosophy? I mean fast by default, you need to explicitly enable anything that costs cycles.
          • jupp0r 1709 days ago
            > vector::operator[] does not check bounds

            vector::at does check bounds, is your argument that programmers shouldn't have a choice there or do you just not know the standard library that well?

            • praptak 1708 days ago
              My argument is that the price of the choice is memory corruption and the benefits seldom outweigh this cost.
              • jupp0r 1706 days ago
                Rust gives you that choice as well (unsafe).
        • bb88 1709 days ago
          > I wonder what kind of C++ you've been writing.

          Last I checked there's still a lot of C++ code in legacy production systems -- and a lot that has never ever used a smart pointer.

          • einpoklum 1709 days ago
            That was exactly my point; GP says they've been writing C++ for the past 6 years, but seems to be talking about legacy code.
            • praptak 1709 days ago
              Nope, I meant modern C++. Granted, it has multiple mechanisms to prevent memory corruption but they are not watertight.
  • einpoklum 1709 days ago
    > Here is a short snippet of some abstract C++ code. As you can see, that is a code from the Windows DLL

    I stopped reading at this point.

    But, ok, I didn't really. I also noticed the code was quite dated / inopportune:

    * Has a "X-er" class - which probably shouldn't exist in the first place.

    * Using raw `delete` and `new`

    * Using unions instead of std::variant's

    * Using C library calls instead of `<algorithm>` functions (memset vs std::fill)

    * Using `rand()` instead of the standard library's facilities (even though it's broken https://www.youtube.com/watch?v=LDPMpc-ENqY&t=329s)

    So... it's outdated and inelegant C++, regardless of any actual errors.

  • Iwan-Zotow 1709 days ago
    Language itself is made exclusively from dark corners and pitfalls.
    • human20190310 1709 days ago
      C++ seems to have this never-ending cycle where it keeps introducing fixes for deficiencies that people have found a way to work around, but which in turn create their own problems...

      E.g. C++11's std::move, which may have partially solved the problem of copying objects (which everyone had their own way of avoiding anyway), but introduced its own puzzles about the validity of the objects after the move.

      • kllrnohj 1709 days ago
        > but introduced its own puzzles about the validity of the objects after the move.

        In practice it's really not a puzzle. Most of the time you're using std::move it's because you don't care about the object you moved from, you're about to drop it on the floor.

        The other times you either manually reset it or just read the docs of the type to find out what it says the state will be after move. It's not really any different from any other method documentation on a class.

        unique_ptr (or other unique_* types) are vastly worth it, which did not otherwise have a way to work prior to C++11's move semantics.

      • einpoklum 1709 days ago
        > C++ seems to have this never-ending cycle where it keeps introducing fixes for deficiencies that people have found a way to work around, but which in turn create their own problems...

        The fixes don't create their own problems. They can and are used as intended, usually with little or no problems. What does happen is that they open up new possibilities and program expressiveness, and those have new issues/challenges.

  • z_open 1709 days ago
    I write C++ for a living but feel out of the loop. Do people still select C++ for new projects? Seems a waste when so many people have put effort into replacing it with something modern. I still learn dark corners and pitfalls almost weekly. It also feels hard to tell when something is going to be a performance hit.
    • jandrewrogers 1709 days ago
      I currently use C++17 for all new projects, in a purely modern style which minimizes the complexity that must be dealt with. It is still a very complex language to learn, I certainly don't know every corner of it.

      For some types of high-performance infrastructure software like database engines, there really isn't a better alternative unfortunately. While there are other non-GC languages like C and Rust, equivalent software requires significantly more lines of code due reduced expressiveness, sometimes with a performance impact, and there are some types of code safety that are difficult to enforce in those languages.

      Because C++ is so expressive in a systems programming context and is rapidly adding valuable features, it makes it difficult for other languages to catch up to the point where there is a direct architectural translation from the latest version of C++ to some other language. I know quite a few shops that actively pull their code bases forward when a new C++ version lands, which is usually pretty painless, to take advantage of new features/safety and added tidiness.

    • munchbunny 1709 days ago
      I generally avoid using C++ for new projects, despite having written a ton of it in the past.

      My problem with C++ is that writing "best practices C++" in 2019 is so different from writing "best practices C++" in 2011 that it may as well be a different language. And if it's going to be a different language, I'm going to prefer a language and toolchain that started from a cleaner slate, such as Rust. For example: C++11 got rvalue and move semantics and that was pretty awesome, but it took me a ton of mental overhead to cleanly reason about interfaces where I wanted to support move semantics. I think Rust's borrowing semantics clean up a lot of C++'s memory ownership/management issues into a simpler model that I just don't have to think as hard about.

      I sometimes still use C++ because it's easier to work with existing code. For example, if I am prototyping something that requires hitting OS API's, C++ is often the best combination of easily calling into the APIs while still getting modern conveniences.

    • jupp0r 1709 days ago
      I write C++ for a living and I think you should give it a try. If you stick to the core guidelines and avoid 95% of the language, it's actually pretty nice. I'd prefer Rust for most things I'd use C++ for, but there are definitely areas where it shines (look at boost::hana, which gives you a unified way of handling compile time and runtime computations, for example).
    • kllrnohj 1709 days ago
      I do, but only because I haven't gotten around to learning Rust yet. There doesn't seem to be all that much happening in the lean & mean with strong C FFI category (aka, the "I want to use the GPU to draw stuff" category).

      I wish I could like D but I just don't, some of the decisions it made I think were mistakes.

      Go's FFI story is unfortunate, and it seems too narrow in target usage anyway.

      C gets a lot of rose-tinted love, but I'd never chose it over modern C++.

      And... well that's sort of it, isn't it? What else is there in this area?

      • jordigh 1709 days ago
        > I wish I could like D but I just don't, some of the decisions it made I think were mistakes.

        Like what? D is everything I ever wanted C++ to be. The only reason I feel compelled to learn Rust is peer pressure, but if it were up to me, D would be the language everyone is getting excited about instead.

        • kllrnohj 1709 days ago
          I don't like D's GC. I don't like its multiple modes (-betterC) which only seem to really exist because enough people didn't like the original design of D. The defaults seem off (like shouldn't functions default as @safe?)

          I get a general impression of D being in just too weird of a space. It's occupying a zone somewhere between C/C++ & Java/C#, and I'm not sure that's a place I ever want to be. I'd rather just use Java/C# if I need D's feature set, and if I don't I don't want to deal with the headaches that result from trying to avoid them (or use -betterC mode which seems to just be what D should have been from the start)

          • jordigh 1709 days ago
            Oh, the GC. I don't understand why everyone seems so hung up on that. I've never noticed it at all. Maybe I just write code where the GC doesn't matter.

            I mean, I do use the GC, it's just that it's never been something that I care about, just like I don't care how exactly new and delete are implemented in C++ (are they just malloc and free wrappers? Eh, who cares.)

            • kllrnohj 1709 days ago
              2 reasons:

              1) Because interop with non-GC (aka, C) code is terrible from a GC'd language. D is better here than other GC'd languages as you can still build smart pointers via RAII, but it still sucks. It's a significant mindset shift in random spots in your code.

              2) Custom allocators can be hugely important in achieving certain optimizations or runtime behaviors. This can either be for just performance reasons or in some cases are mandatory for correctness such as if it's a real-time thread.

            • danbolt 1709 days ago
              A lot of performance-sensitive code involved in game development usually eschews garbage collection, as pause times can affect per-frame performance budgets non-deterministically.

              It's partially why Rust and modern C++ are getting a lot of attention in that space, as they provide a lot of higher-level niceties but don't require the overhead of a GC.

    • homie 1709 days ago
      It really depends on the project. A lot of times that modern thing you're working with is just a wrapper around some C/C++ library (unless you're doing web dev - then there's a whole other set of issues).
  • altmind 1709 days ago
    I really like C++FQA that covers a lot of inconsistencies and shortcomings in C++ design https://yosefk.com/c++fqa/
    • jcranmer 1709 days ago
      The C++FQA is not a terribly good resource. It's centered around a point-by-point rebuttal of a FAQ resource that's kind of outdated and of questionable quality to begin with. Modern C++ revisions (starting with C++11) have gone a long way to cleaning up warts in C++ and fixing issues, and remains pretty unaddressed in the FQA, which is essentially unchanged from ~2009. As one concrete example, people complaining about C++ ABI incompatibility are complaining about something that hasn't been a problem for at least 15 years.
  • hellofunk 1709 days ago
    This is an ugly code snippet. No one should write C++ that looks like this and if they do they are asking for problems.
  • otabdeveloper4 1709 days ago
    > Windows WinAPI shite

    Are you kidding me? What's next, EBCDIC and zOS?

    • f00zz 1709 days ago
      Yeah. I was expecting something about C++, but the article is mostly about Win32 dynamic linking issues.