Live data from Hacker News

Defer available in gcc and clang

gustedt.wordpress.com

201–210 of 262 posts

Re: Defer available in gcc and clang

#201
post #122
post #113

Earlier quoted context omitted.

In my opinion, it's the initialization part of RAII which is really powerful and still missing from most other languages. When implemented properly, RAII completely eliminates a whole class of bugs related to uninitialized or partially initialized objects: if all initialization happens during construction, then you either have a fully initialized correct object, or you exit via an exception, no third state. Additiona…

This sounds like a nice theoretical benefit to a theoretical RAII system (or even a practical benefit to RAII in Rust), but in C++, I encounter no end of bugs related to uninitialized or partially initialized objects. All primitive types have a no-op constructor, so objects of those types are uninitialized by default. Structs containing members of primitive types can be in partially initialized states where some memb…

I think you are both right, to some degree.

It's been some since I have used C++, but as far as I understand it RAII is primarily about controlling leaks, rather than strictly defined state (even if the name would imply that) once the constructor runs. The core idea is that if resource allocations are condensed in constructors then destructors gracefully handle deallocations, and as long you don't forget about the object (_ptr helpers help here) the destructors get called and you don't leak resources. You may end up with a bunch of FooManager wrapper classes if acquisition can fail (throw), though. So yes, I agree with your GP comment, it's the deterministic destruction that is the power of RAII.

On the other hand, what you refer to in this* comment and what parent hints at with "When implemented properly" is what I have heard referred to (non English) type totality. Think AbstractFoo vs ConcreteFoo, but used not only for abstracting state and behavior in class hierarchy, but rather to ensure that objects are total. Imagine, dunno, database connection. You create some AbstractDBConnection (bad name), which holds some config data, then the open() method returns OpenDBCOnnection() object. In this case Abstract does not even need to call close() and the total object can safely call close() in the destructor. Maybe not the best example. This avoids resources that are in an undefined state.

Re: Defer available in gcc and clang

#202
post #192

Earlier quoted context omitted.

The example allocates an SDL_Surface large enough to fit the text string each iteration. Granted, you could do a pre-pass to find the largest string and allocate enough memory for that once, then use that buffer throughout the loop. But again, what do you gain from that complexity?

> The example allocates an SDL_Surface large enough to fit the text string each iteration. Impossible without knowing how much to allocate, which you indicate would require adding a bunch of complexity. However, I am willing to chalk that up to being a typo. Given that we are now calculating how much to allocate on each iteration, where is the meaningful complexity? I see almost no difference between: while (next())…

>> The example allocates an SDL_Surface large enough to fit the text string each iteration.

> Impossible without knowing how much to allocate

But we do know how much to allocate? The implementation of this example's RenderTextToSurface function would use SDL functions to measure the text, then allocate an SDL_Surface large enough, then draw to that surface.

> I see almost no difference between: (code example) and (code example)

What? Those two code examples aren't even in the same language as the code I showed.

The difference would be between the example I gave earlier:

    stringTextures := []SDLTexture{}
    for _, str := range strings {
        surface := RenderTextToSurface(str)
        defer surface.Destroy()
        stringTextures = append(stringTextures, surface.CreateTexture())
    }
and:

    surface := NewSDLSurface(0, 0)
    defer surface.Destroy()
    stringTextures := []SDLTexture{}
    for _, str := range strings {
        size := MeasureText(s)
        if size.X > surface.X || size.Y > surface.Y {
            surface.Destroy()
            surface = NewSDLSurface(size.X, size.Y)
        }

        surface.Clear()
        RenderTextToSurface(surface, str)
        stringTextures = append(stringTextures, surface.CreateTextureFromRegion(0, 0, size.X, size.Y))
    }
Remember, I'm talking about the API to a Go wrapper around SDL. How the C code would've looked if you wrote it in C is pretty much irrelevant.

I have to ask again though, since you ignored me the first time: what do you gain? Text rendering is really really slow compared to memory allocation.

Re: Defer available in gcc and clang

#203
post #198

I have a personal aversion to defer as a language feature. Some of this is aesthetic. I prefer code to be linear, which is to say that instructions appear in the order that they are evaluated. Further, the presence of defer almost always implies that there are resources that can leak silently. I also dislike RAII because it often makes it difficult to reason about when destructors are run and also admits accidental l…

If you dislike things happening out of lexical order, I expect must already dislike C because of one of its many notorious footguns, which is that the evaluation order of function arguments is implementation-defined.

About RAII, I think your viewpoint is quite baffling. Destructors are run at one extremely well-defined point in the code: `}`. That's not hard to reason about at all. Especially not compared to often spaghetti-like cleanup tails. If you're lucky, the team does not have a policy against `goto`.

Re: Defer available in gcc and clang

#204
post #195

Earlier quoted context omitted.

You'd probably have to be doing something pretty unusual to not use a worker queue. Your "P.S." point being a perfect case in point as to why. If you have a legitimate reason for doing something unusual, it is fine to have to use the tools unusually. It serves as a useful reminder that you are purposefully doing something unusual rather than simply making a bad design choice. A good language makes bad design decision…

You have now transformed the easy problem of "iterate through some files" into the much more complex problem of either finding a work queue library or writing your own work queue library; and you're baking in the assumption that the only reasonable way to use that work queue is to make each work item exactly one file. What you propose is not a bad solution, but don't come here and pretend it's the only reasonable sol…

> It is telling that you keep insisting

Keep insisting? What do you mean by that?

> when you haven't even responded to my core argument that: sometimes sequential is fast enough.

That stands to reason. I wasn't responding to you. The above comment was in reply to nasretdinov.

Re: Defer available in gcc and clang

#205
post #202

Earlier quoted context omitted.

> The example allocates an SDL_Surface large enough to fit the text string each iteration. Impossible without knowing how much to allocate, which you indicate would require adding a bunch of complexity. However, I am willing to chalk that up to being a typo. Given that we are now calculating how much to allocate on each iteration, where is the meaningful complexity? I see almost no difference between: while (next())…

>> The example allocates an SDL_Surface large enough to fit the text string each iteration. > Impossible without knowing how much to allocate But we do know how much to allocate? The implementation of this example's RenderTextToSurface function would use SDL functions to measure the text, then allocate an SDL_Surface large enough, then draw to that surface. > I see almost no difference between: (code example) and (co…

> Remember, I'm talking about the API to a Go wrapper around SDL.

We were talking about using malloc/free vs. a resizable buffer. Happy to progress the discussion towards a Go API, however. That, obviously, is going to look something more like this:

    renderer := SDLRenderer()
    defer renderer.Destroy()
    for _, str := range strings {
        surface := renderer.RenderTextToSurface(str)
        textures = append(textures, renderer.CreateTextureFromSurface(surface))
    }
I have no idea why you think it would look like that monstrosity you came up with.

Re: Defer available in gcc and clang

#206
post #200
post #192

Earlier quoted context omitted.

The example allocates an SDL_Surface large enough to fit the text string each iteration. Granted, you could do a pre-pass to find the largest string and allocate enough memory for that once, then use that buffer throughout the loop. But again, what do you gain from that complexity?

I think I've been successfully nerd sniped. It might be preferable to create a font atlas and just allocate printable ASCII characters as a spritesheet (a single SDL_Texture* reference and an array of rects.) Rather than allocating a texture for each string, you just iterate the string and blit the characters, no new allocations necessary. If you need something more complex, with kerning and the like, the current ver…

Completely depends on context. If you're rendering dynamically changing text, you should do as you say. If you have some completely static text, there's really nothing wrong with doing the text rendering once using PangoCairo and then re-using that texture. Doing it with PangoCairo also lets you do other fancy things like drop shadows easier.

Re: Defer available in gcc and clang

#207
post #202

Earlier quoted context omitted.

>> The example allocates an SDL_Surface large enough to fit the text string each iteration. > Impossible without knowing how much to allocate But we do know how much to allocate? The implementation of this example's RenderTextToSurface function would use SDL functions to measure the text, then allocate an SDL_Surface large enough, then draw to that surface. > I see almost no difference between: (code example) and (co…

> Remember, I'm talking about the API to a Go wrapper around SDL. We were talking about using malloc/free vs. a resizable buffer. Happy to progress the discussion towards a Go API, however. That, obviously, is going to look something more like this: renderer := SDLRenderer() defer renderer.Destroy() for _, str := range strings { surface := renderer.RenderTextToSurface(str) textures = append(textures, renderer.CreateT…

> No. We were talking about using malloc/free vs. a resizable buffer.

No. This is a conversation about Go. My example[1], that you responded to, was an example taken from a real-world project I've worked on which uses Go wrappers around SDL functions to render text. Nowhere did I mention malloc or free, you brought those up.

The code you gave this time is literally my first example (again, [1]), which allocates a new surface every time, except that you forgot to destroy the surface. Good job.

Can this conversation be over now?

[1] https://news.ycombinator.com/item?id=47088409

Re: Defer available in gcc and clang

#208
post #207

Earlier quoted context omitted.

> Remember, I'm talking about the API to a Go wrapper around SDL. We were talking about using malloc/free vs. a resizable buffer. Happy to progress the discussion towards a Go API, however. That, obviously, is going to look something more like this: renderer := SDLRenderer() defer renderer.Destroy() for _, str := range strings { surface := renderer.RenderTextToSurface(str) textures = append(textures, renderer.CreateT…

> No. We were talking about using malloc/free vs. a resizable buffer. No. This is a conversation about Go. My example[1], that you responded to, was an example taken from a real-world project I've worked on which uses Go wrappers around SDL functions to render text. Nowhere did I mention malloc or free, you brought those up. The code you gave this time is literally my first example (again, [1]), which allocates a new…

I invite you to read the code again. You missed a few things. Notably it uses a shared memory buffer, as discussed, and does free it upon defer being executed. It is essentially equivalent to the second C snippet above, while your original example is essentially equivalent to the first C snippet.

Re: Defer available in gcc and clang

#209
post #195

Earlier quoted context omitted.

You have now transformed the easy problem of "iterate through some files" into the much more complex problem of either finding a work queue library or writing your own work queue library; and you're baking in the assumption that the only reasonable way to use that work queue is to make each work item exactly one file. What you propose is not a bad solution, but don't come here and pretend it's the only reasonable sol…

> It is telling that you keep insisting Keep insisting? What do you mean by that? > when you haven't even responded to my core argument that: sometimes sequential is fast enough. That stands to reason. I wasn't responding to you. The above comment was in reply to nasretdinov.

Your comment was in reply to nasretdinov, but its fundamental logic ignores what I've been telling you this whole time. You're pretending that the only solution to iterating through files is a work queue and that any solution that does a synchronous open/close for each iteration is fundamentally bad. I have told you why it isn't: you don't always need the performance.

Re: Defer available in gcc and clang

#210
post #185
post #94

Earlier quoted context omitted.

Not to mention that the `scope_success` and `scope_failure` variants have to use `std::uncaught_exceptions()`, which is hostile to codegen and also has other problems, especially in coroutines. C++ could get exception-aware variants of language defer.

What C++ really needs is an automated way to handle exceptions in destructors, similar to how Java does in its try-with-resources finally blocks.

While not automated, you can make use of function-try-blocks, e.g.:

    struct Example {
        Example() = default;

        ~Example()
        try {
        // elease resources for this instance
        } catch (...) {
            // take care of what went wrong in the whole destructor call chain
        }
    };
-- https://cpp.godbolt.org/z/55oMarbqY

Now with C++26 reflection, one could eventually generate such boilerplate.

Post reply on HN