1. 2
  1. 5

    You have to explicitly use smart pointers in C++

    This is not a good thing.

    The memory management with std::unique_ptr has by design no overhead in performance or memory compared to a raw pointer

    This is not true: https://www.youtube.com/watch?v=rHIkrotSwcc

    Most of the article then talks about RAII, which isn’t exclusive to C++.

    1. 3

      Specifically, unique_ptr can’t be passed in registers, but has to be on stack, due to ABI backwards compatibility. Weirdly, C++ wasn’t supposed to have a stable ABI, but it also refuses to change the one it has.

      1. 1

        C++ doesn’t have an ABI, let alone a stable one. C++ implementations have ABIs, and it’s those implementations that refuse to break backwards binary compatibility. The C++ standards committee doesn’t require any ABI stability, they just don’t add features that force vendors to break compatibility. There’s a big difference there.

        I have never seen any hard evidence that unique_ptr being unable to be passed in registers actually causes performance degradation. That doesn’t mean it doesn’t, of course, but it would be nice to see some evidence given how often this is brought up as an issue. All he shows in that video, if I remember it correctly, is that the generated assembly is larger, but not that this actually necessarily causes performance issues. For all we know it might actually be more performant due to less register pressure. The stack is certainly in L1 cache anyway so it’s not much slower than a register. If you’re passing a unique_ptr to the function, the performance cost of actually dereferencing it is likely to be much larger than the cost of passing it on the stack instead of in registers.

        1. 3

          C++ doesn’t have an ABI, let alone a stable one.

          The thing is, the C++ committee (WG21) has decided in Prague this year that C++ won’t break ABI (explained in the article link in prev post). So C++ is in a funny situation where it doesn’t guarantee a stable ABI, but also all standard library fixes and performance improvements that would require breaking ABI stability have been explicitly ruled out.

          1. 1

            Which is exactly what I said: the committee will not force vendors to break ABI, but that doesn’t mean they’re forbidding them from doing so.

            This is also nothing new.

      2. 2

        This is not a good thing.

        That’s pretty subjective. I think a lot of people would consider it a good thing. Having the ownership graph encoded explicitly in the types of the pointers gives some big benefits in terms of understanding that ownership graph over the GC-languages model of just having a big bag of objects all pointing at each other. I personally find it very easy to get lost in a C# or Java codebase, always asking the question: what code is actually responsible for this object? Who owns this object? Who is allowed to modify it? When should it no longer be touched? Having garbage collection doesn’t stop you from having to, for example, close a file at a well-defined time and then make sure not to touch the file object after that.

        1. 5

          I understood it differently: explicit is fine, which is why C++’s ambiguously-owned raw pointers are a bad default. In contrast Rust is very explicit with ownership, but the default basic syntax is given to single ownership and moves, and other cases need noisier Rc or raw pointers.

          1. 3

            A clear ownership graph is a good thing indeed, when one exists, but requiring that one exists is not good.

            GC permits some styles of programming where the ownership graph isn’t complete. For example, I have a complicated data structure (a function expressed as SSA nodes) where the entire tree is owned, but individual nodes in the tree don’t really have owners, and none of the models I considered ensured that owners were created before the nodes they own.

            Most of the program uses ownership. I ended up using a lot of time on ownership for that data structure that I could have spend on business logic.

            1. 1

              the entire tree is owned, but individual nodes in the tree don’t really have owners

              In those cases the graph owns the nodes, presumably storing them in a vector, while the nodes should just have non-owning raw pointers to each other.

              none of the models I considered ensured that owners were created before the nodes they own.

              That’s pretty trivial, isn’t it? The owner should be the thing creating the nodes it owns. Something cannot create before it itself exists.

              1. 4

                I thought it was trivial too. I only realised my naïvete when Valgrind showed me that one of those raw pointers was dereferenced too late, and the root problem behind that late deferencing wasn’t easily fixed.

                Mixing raw pointers and std::shared_ptr/unique_ptr is partial RAII. Partial RAII relies too much on developer smarts for me.

            2. 1

              what code is actually responsible for this object? Who owns this object?

              The runtime.

              close a file at a well-defined time and then make sure not to touch the file object after that.

              I don’t think this is a very good example. Both C# and Java have ways of opening a file and ensuring it gets closed when you’re done with it, and I struggle to think why anyone would do anything else like return it from a function instead.

              1. [Comment from banned user removed]

                1. 1

                  inane and stupid

                  Why? It’s the easiest way to program, let the runtime handle it.

                  Many different objects could own a file and control its lifespan, not just some lexical scope.

                  Perhaps. I can count on the fingers of one hand the number of times I’ve needed to do that.

                  1. 0

                    Why? It’s the easiest way to program, let the runtime handle it.

                    Are you trolling? I’m not talking about who is responsible for freeing the memory of the object. I’m talking about what code is responsible for the object, what code owns the object. A network connection cannot be owned by ‘the runtime’ unless you want to just lazily clean up network connections whenever, run out of available file descriptors and embarrass yourself when you try to explain to people that ‘the runtime owns the network connection, so it’s not my fault!’

                    The ‘easiest way to program’ is indeed to just not actually clean up after yourself, and hope that the language runtime will be lucky enough to have been written well by actually competent programmers who take into account things like running the GC before reporting file descriptor exhaustion. But can you actually rely on that? Do common language runtimes actually do that? Is that sufficient to handle the problem?

                    Perhaps. I can count on the fingers of one hand the number of times I’ve needed to do that.

                    If all you have is a hammer, every problem looks like a nail. When your only available tool for managing resources is try-finally or some equivalent scope-based lifespan, then of course you aren’t going to approach problems from the perspective that ownership exists. You demonstrate in the previous line of your comment that you don’t even understand the very concept of ownership!

                    Have you never called a function that returned a resource (a file, a network connection, etc.)? Guess what, that resource wasn’t freed in the same scope it was created in. It’s almost as if you don’t always want to tie object lifetime to a single lexical scope or something.

          2. 4

            I used to pretty well agree with all this… but then I spent a good amount of time just trying to figure out how a library handled a more complex memory graph. The documentation wasn’t clear so I had to trace through the source and hope I didn’t misread. Sure, C++ had a solution… but it would have been so much simpler if it was just an automatic garbage collector.

            My view now is to use the GC by default for memory, RAII for other stuff, and then special case other memory strategies when it is proven necessary/appropriate. The cost of GC in most circumstances is pretty small in the real world and the benefits outweigh them; I’ve learned to love it.

            1. 1

              Heh. I feel so smug now.

              I’ve posted a few times in thhose Monday threads about a java compiler I’m writing. (Has run the simplest of programs, next Monday I might run my first benchmark.) And when I read that modernescpp blog posting, my reaction is: God that’s a lot of tedious boilerplate, and it’ll be slower than the garbage collector in my compiler too. Extra boilerplate and a performance penalty. I feel so smug now.