The abhorrent πŸ”₯ rites of C


  • Winner of the 2016 Presidential Election

    I'll let you know when I find my old test code again. I honestly don't remember what the edge case was, but I remember finding an edge case a while ago and playing around with test code to reproduce it.



  • @dkf said:

    @Steve_The_Cynic said:
    It would look like this in C++ (to exploit the implicit finally):

      try
      {
        FooDisabler foodisable(foo);
        foo.LoadDataFromSomewhere(somewhere);
      }
      catch( ... )
      {
    // Explode
      }
    ```</blockquote>
    
    No, it would look like this:
    ```cpp
    {
        FooDisabler foodisable(foo);
        foo.LoadDataFromSomewhere(somewhere);
    }
    

    plus you'd have to have that auxiliary class defined somewhere.

    You're right, there's no special need for the exception handler, unless you actually need to handle the exceptions. And the initial challenge was to do RAII without the thing that makes RAII even possible for objects with explicit-close states (like the aforementioned disable/enable for "updates").

    But in the end, it's best to have LoadDataFromSomewhere do the disable/enable (or to assert that it has been done if you want to be able to suspend updates while you do several things and then reenable, and if you want that, a FooDisabler class is the way to go).

    Short version: RAII and its accompanying syntax noise are C++ idioms, just like try/finally are idioms of languages that can't do RAII in a C++ style, and must have explicit calls, frequently because of delayed-destruct.



  • @Steve_The_Cynic said:

    It would look like this in C++ (to exploit the implicit finally):
    FooDisabler foodisable(foo);

    Yeah, that's the whole "you have to do this big ugly hack involving creating an entire class to re-implement finally on top of RAII because RAII is a big ugly abstraction inversion" bit I was talking about before.

    @Steve_The_Cynic said:

    Or, indeed, you can just put the DisableUpdates/EnableUpdates calls inside Foo::LoadDataFromSomewhere(), where they belong.

    I put one line inside the try block instead of several for the sake of a simple example. In real-world code it's frequently like you describe, but your objection doesn't solve the problem, it just moves it around. You just end up requiring exactly the same pattern inside Foo::LoadDataFromSomewhere() instead.

    @LaoC said:

    I had forgotten what malloc is called in Pascal, and the first tutorial I googled used code pretty much like that.

    Which doesn't change the fact that people don't use string pointers in Pascal.

    @LaoC said:

    Of course you can just insert a q := nil to silence the compiler and simulate the more common case that someone passes you a NULL by accident. freemem doesn't care.

    In which case you have a completely different problem: assignment to a null pointer without checking your arguments. That problem exists in every language where you have reference types that are nullable by default; there's nothing particularly noteworthy about Pascal in that particular aspect of its type system. (And if you freemem a nil, nothing happens.)

    @LaoC said:

    Don't pretend C didn't work. It's just hard to make it safe.

    No. People accomplish "hard" all the time. C safety is beyond hard. It's a problem so inhumanly hard that even people with decades of experience keep making the same mistakes. See for example the Heartbleed bug. The guy who screwed that up is obviously a very experienced developer who knows what he's doing, but to err is human. The problem is that in C it's very easy to do the wrong thing and get away with it, and that can't be fixed because backwards compatibility.

    @LaoC said:

    Of course there is a good case to be made for writing everything but a pretty thin layer over the hardware in something higher-level, but you really don't want to set up MMU tables or do ACPI shit in $HLL

    I would. And then if the compiler didn't generate good enough code from my $HLL, I'd fix the compiler. One of the nice things about having a $HLL that restricts some of the ugly low-level things you can do is that it opens itself up to new optimization strategies that aren't available in bare-metal land because you can't tell if someone isn't doing something that would explode if your optimizer looks at it funny.

    @asdf said:

    RAII is anything but an ugly hack.

    That's precisely what RAII is.


  • Winner of the 2016 Presidential Election

    @Mason_Wheeler said:

    > RAII is anything but an ugly hack.

    That's precisely what RAII is.

    I'd rather say that AutoClosable is a hack for languages which cannot guarantee deterministic object destruction.



  • What's AutoCloseable?


  • Winner of the 2016 Presidential Election

    I'm probably going to regret this question, but: What's the Pascal alternative you're talking about?



  • A close() method declared throws Exception

    Wow. Much facepalm here.

    ETA: Bonus reading: Clean-up functions can’t fail because, well, how do you clean up from a failed clean-up?


  • Winner of the 2016 Presidential Election

    @asdf said:

    https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html

    Funny side note: Oracle's own documentation is wrong.

    static String readFirstLineFromFile(String path) throws IOException {
        try (BufferedReader br =
                       new BufferedReader(new FileReader(path))) {
            return br.readLine();
        }
    }
    

    The FileReader in the code snippet above will not be closed if the BufferedReader constructor throws.



  • @asdf said:

    I'm probably going to regret this question, but: What's the Pascal alternative you're talking about?

    OK, as near as I can tell, that appears to be a Java equivalent of C#'s using pattern, with close() in the place of IDisposable.Dispose(). The Pascal equivalent is to just write a destructor on your class, (equivalent to close/Dispose,) and dispose of it with a try/finally block.


  • Winner of the 2016 Presidential Election

    @Mason_Wheeler said:

    The Pascal equivalent is to just write a destructor on your class, (equivalent to close/Dispose,) and dispose of it with a try/finally block.

    What's the big difference to C++, then?



  • @asdf said:

    What's the big difference to C++, then?

    The difference is that there are many other uses for a finally block that don't involve destroying an object, and C++ does not support them.


  • Winner of the 2016 Presidential Election

    @Mason_Wheeler said:

    The difference is that there are many other uses for a finally block that don't involve destroying an object, and C++ does not support them.

    So… Your point is that stuff like std::unique_lock is a hack, in your opinion? I disagree, I find it very convenient that I can pass a resource object around (through many scopes, if necessary) and that the resource will still be released automatically at the correct point in time, without me having to remember to put a finally block anywhere.


  • Discourse touched me in a no-no place

    @Steve_The_Cynic said:

    You're right, there's no special need for the exception handler, unless you actually need to handle the exceptions.

    The other languages were just passing them through too. YourPoint = std::moot;


  • Discourse touched me in a no-no place

    @asdf said:

    The FileReader in the code snippet above will not be closed if the BufferedReader constructor throws.

    That's why you can do this:

    try (Reader fr = new FileReader(path);
         BufferedReader br = new BufferedReader(fr)) {
      // ...
    }
    

    (Pendantry point: I can't remember if it is ; or , that separates clauses.)


  • Winner of the 2016 Presidential Election

    I know, it's still funny that Oracle gets it wrong in the document that introduces the feature.



  • @asdf said:

    So… Your point is that stuff like std::unique_lock is a hack, in your opinion? I disagree, I find it very convenient that I can pass a resource object around (through many scopes, if necessary) and that the resource will still be released automatically at the correct point in time, without me having to remember to put a finally block anywhere.

    If I'm understanding the point of unique_lock properly, it appears to be an object wrapper around a lock whose destructor will automatically unlock the lock in question before destroying it. Regardless of anything else, I can't help but say that looks like a hack, because why doesn't the base lock class do that in its own destructor, without needing a wrapper?



  • @asdf said:

    Just don't use "raw" pointers in the first place. unique_ptr and shared_ptr will do the right thing for you.

    No, they wont - at least not with manual intervention. For unique_ptr, you need to use the array type (i.e., unique_ptr<FooType[]> instead of unique_ptr<FooType>). For shared_ptr, you have to define a custom deleter (at least until C++17), since array support for shared_ptrs was "forgotten".

    (I've seen too much code do shared_ptr<FooType> foos( new FooType[number] );, which is -of course- completely broken.)


  • Winner of the 2016 Presidential Election

    This post is deleted!


  • @asdf said:

    Anyway, even if you called this a hack (instead of just questionable standard library design), it still doesn't mean that the concept RAII is a hack.

    OK, seriously, what part of "abstraction inversion" are you not understanding here?


  • Winner of the 2016 Presidential Election

    @cvi said:

    No, they wont - at least not with manual intervention. For unique_ptr, you need to use the array type (i.e., unique_ptr<FooType[]> instead of unique_ptr<FooType>).

    Of course you need to construct the object correctly, duh. My point was that unique_ptr remembers how it's supposed to release the memory/resource.

    @cvi said:

    I've seen too much code do shared_ptr<FooType> foos( new FooType[number] );

    Now this code couldn't possibly have been written by a person who understands C++11. For starters, you should never call the shared_ptr constructor directly, but use make_shared. Someone didn't bother to read the documentation.



  • @ronin said:

    You're right, it doesn't. I didn't have a C compiler handy at the moment and it was written in a rush. The intention was to return an int and to assing the newly created pointer to the old pointer.

    Lets say that explained the return value being a pointer. A simple typo. How do you explain dereferencing the out parameter? You shouldn't need the compiler to babysit you there. That's something you should be writing every time you write a function. You can't get a trivial function to compile without running it past the compiler?

    Not to mention that you complained about me using C++ idioms, and then rewrote the exact same function (except for the broken parts) that I did. You just removed the std:: from malloc and free.

    @ronin said:

    Its a special type of pointer.
    Really? Have you tried telling the compiler that? Because as far as the compiler knows, it's just a regular pointer to a FILE struct. See, it's not the pointer that's special, it's the state managed by the FILE struct that requires special handling. And FILE isn't even the only case.

    In miniz (a C library for managing zip files) you may extract files with mz_zip_reader_extract_to_heap, which returns a regular void pointer, and have to free the memory with mz_free because what allocator was used can depend on compilation flags. Better hope that everyone you pass that regular void pointer to knows that.

    In freetype2 (a C library for reading font files) you initialize a handle to the library with FT_Init_FreeType and have to call FT_Done_FreeType on that handle when you're done. And each additional type has its own FT_Init and FT_Done functions.

    In openSSL (a C library for secure communications) you initialize a RSA key pointer with PEM_read_bio_RSAPrivateKey and free it with RSA_free. There are also other types that have their own init and free functions.

    And the compiler won't help you when you pass any of those pointers to free, because as far as it is concerned, they're all just pointers and free is a function that receives void pointers, which they can all be casted to.

    And you need the compiler to hold your hand through a trivial eight line function.

    @ronin said:

    Is not more complicated than doing .close() on a file object.
    Except no one does that unless they want to reuse the file object, since it will be called by the destructor automatically on every path out of the containing scope.

    @ronin said:

    So Donald Knuth, Theo DeRaadt, Linus Torvalds and Bertrand Meyer are all wrong to distrust C++ and you are right, then?
    Yes. Or put another way: Why are clang and gcc both written in C++? The guys writing these compilers are the ones that know the C and C++ standards best. They know every nook and cranny of both languages because they are the ones implementing the standards. In practice, the standards are what they say they are, and if you don't like it, you can go write your own optimizing compiler.

    And despite knowing both languages as well as anyone can, they both chose C++. I'm pretty sure clang started out as C++ (and clang's back-end, LLVM, is also written in C++), and gcc switched. So if the guys writing your C compiler choose to do it in C++, maybe they know something you don't?

    @ronin said:

    C++ has worse spatial locality than C.
    And if that matters for some part of the application, that part can be written in functional style, or structured, or any other paradigm that best matches the performance requirements of that bit of code, while still leveraging the superior (although not even all that great) type system for compiler-checked safety.

    @ronin said:

    I wasn't aware of a C++11 feature, which uses object oriented parts to do its job.
    No it doesn't. It uses destructors, which are a feature that is separate from object orientation (in fact, most other object oriented languages don't even have them and rely on writing finally clauses everywhere). You can actually use unique_ptr with non-exceptional code. Here's a C-like function managing a couple of allocated objects with malloc and free, using unique_ptrs for safety: https://goo.gl/JQe3ri

    Both bool cptr(int*) and bool cppptr(int*) do the same thing, you can even compare the assembly to check. But cppptr is simpler to read, and safer. To prove this, did you notice the subtle leak I introduced into the cptr function? Did I introduce a leak, or am I just making you check again for giggles? If I did, can you say what it is and under what circumstances the leak would manifest? It's a straight forward function that only manages eight bytes of allocated memory. It should be pretty simple to prove if it's right or not.

    @ronin said:

    The claim that C++ can replace it for ALL THE THINGS has to have some foundations.
    It does: C++ can do everything C can do, in the same way C does it if necessary. Can you show something you can write in C that can't be done in C++? I just linked above a function that is made safer simply by using smart pointers, without relying on exceptions or any other expensive features. It's even faster, if you look at the assembly (it has a couple fewer instructions because it doesn't need to keep a result value).

    Give just one example of C code that C++ can't do equally or better.

    @jmp said:

    I wouldn't expect unique_ptr to have any significant overhead compared to malloc()/free(), and it has the advantage of being automatic.
    You can use unique_ptr with malloc/free, and it makes the resource management trivial, as I showed above. Even if you tie one of C++'s arms behind its back and disable exceptions, it's still a safer, better alternative to C.

    @Mason_Wheeler said:

    Don't believe me? Try translating this simple Object Pascal code into C++ without having to create any extraneous classes for the purpose of making RAII work:
    [...]
    And you can't do it in C++, at least not in any way that's anywhere near this simple and readable.
    Having finally clauses all over the place is terrible. It's just a step up from writing manual memory management. Why write it a hundred times, when you can write it once? But still, if I wanted to do exactly what you do there (maybe I hit my head), I need one trivial utility class and one trivial utility function that are written just once and can be used all over the application. It hardly hurts readability at the call site.

    template <class T>
    struct FinallySt {
      T t_;
      FinallySt(T t) : t_(std::move(t)){}
      ~FinallySt() { t_(); }
    };
    
    template <class T>
    auto Finally(T t) {
      return FinallySt<T>(std::move(t));
    }
    
    struct Foo {
      void EnableUpdates();
      void DisableUpdates();
    };
    
    void LoadDataInto(Foo&);
    
    void f() {
      Foo foo;
      foo.DisableUpdates();
      {
        auto guard = Finally([&foo](){
          foo.EnableUpdates();
        });
        LoadDataInto(foo);
      }  
    }
    

    There are probably better ways to do it than recreating an inferior idiom, but it can be done. Of course, you can use explicit try/catch as well, but I don't really like those.

    @dkf said:

    One of the better code-structure inventions of recent years has been the using/try-with-resources of C# and Java.
    Can you explain what is the advantage of having to manually type something you have to do anyway every time? "The programmer may not be aware of the cost"? Because garbage collection is cheap and predictable? The try/using just tells the system you are done with it, the system then decides to clean it up whenever it feels like. How is that less concealing than a destructor that runs deterministically where it was called?

    @PJH said:

    Now - do I need to use delete or delete[] on this pointer...? Oh - hang on, I used placement new - I don't need to do anything with it... Now that other pointer from legacy code that was malloc()'d on the other hand...
    unique_ptr handles that for you. No need for you to worry about it. Even the malloc pointer (I wrote an example above) and the placement newed object (which does need to be cleaned up by calling its destructor explicitly, preferably before you clean up the memory where you constructed it).

    @cvi said:

    No, they wont - at least not with manual intervention. For unique_ptr, you need to use the array type (i.e., unique_ptr<FooType[]> instead of unique_ptr<FooType>). For shared_ptr, you have to define a custom deleter (at least until C++17), since array support for shared_ptrs was "forgotten".
    You're using them wrong. You shouldn't type new anywhere.

    auto myPtr = std::make_unique<int>(); // This gives you a single int, calls delete when it's done.
    auto myArrayPtr = std::make_unique<int[]>(10); // This gives you an array of 10 ints, calls delete[] when it's done.
    

    Similarly for shared_ptr, you have make_shared.


  • Winner of the 2016 Presidential Election

    @Mason_Wheeler said:

    OK, seriously, what part of "abstraction inversion" are you not understanding here?

    I don't understand why you think RAII is abstraction inversion.

    And I especially don't understand why you think peppering your code with try ... finally (that you have to remember to put in every single scope where you use a resource) is any better.



  • @Mason_Wheeler said:

    If I'm understanding the point of unique_lock properly, it appears to be an object wrapper around a lock whose destructor will automatically unlock the lock in question before destroying it. Regardless of anything else, I can't help but say that looks like a hack, because why doesn't the base lock class do that in its own destructor, without needing a wrapper?

    You have the std::mutex, which is the mutex that guards a resource. Before you mess with the resource, you need to acquire the lock. You can do so with std::mutex::lock(). When you're done with the resource, you release the lock (std::mutex::unlock()).

    Now, instead of doing the locking manually (which typically also has problems with being exception unsafe), you can use std::unique_lock. So, creating a unique lock (std::unique_lock myLock(myMutex);) will acquire the lock on the mutex (myMutex), and release it automatically when the lock myLock is destroyed. (So, the constructor of unique_lock calls lock() on the mutex; and the destructor calls unlock()).

    But, unique_lock is not a wrapper around a different class.



  • @asdf said:

    Now this code couldn't possibly have been written by a person who understands C++11. For starters, you should never call the shared_ptr constructor directly, but use make_shared. Someone didn't bother to read the documentation.

    There's no make_shared for arrays.

    Edit: also paging @Kian

    Edit2: I'll give you the make_unique<Foo[]>(num), though. I'm still stuck with C++11 without the C++14 extensions, and the make_unique<>() emulation that I'm using doesn't implement the array version. :-(


  • Winner of the 2016 Presidential Election

    @cvi said:

    There's no make_shared for arrays.

    TIL. Never noticed, because I never needed it.



  • @Kian said:

    In miniz (a C library for managing zip files) you may extract files with mz_zip_reader_extract_to_heap, which returns a regular void pointer, and have to free the memory with mz_free because what allocator was used can depend on compilation flags. Better hope that everyone you pass that regular void pointer to knows that.

    If your code is freeing pointers that someone else passed in from the outside, that's the problem right there. The only time that's a good idea is when the code in question is a collection object with explicit "this collection owns its members" semantics, and you're not likely to mix that with raw data extracted from a zip archive.

    @Kian said:

    But still, if I wanted to do exactly what you do there (maybe I hit my head), I need one trivial utility class and one trivial utility function that are written just once and can be used all over the application. It hardly hurts readability at the call site.

    So to replace a simple idiom that takes 4 lines of code, you propose one that takes 10 to set up, (off in some other place,) and 5 lines every time you want to invoke it--and writes the logic out of order to boot--and call that simpler and easier to read? :facepalm:

    @asdf said:

    I don't understand why you think RAII is abstraction inversion.

    Because that's literally the definition of abstraction inversion: implementing a high-level construct on top of a lower-level construct and then not providing access to the more fundamental construct, forcing people who need it to inefficiently reimplement it on top of the high-level construct.

    @asdf said:

    And I especially don't understand why you think peppering your code with try ... finally (that you have to remember to put in every single scope where you use a resource) is any better.

    It becomes automatic very quickly when you actually try it. (No pun intended.)

    @cvi said:

    Now, instead of doing the locking manually (which typically also has problems with being exception unsafe), you can use std::unique_lock. So, creating a unique lock (std::unique_lock myLock(myMutex);) will acquire the lock on the mutex (myMutex), and release it automatically when the lock myLock is destroyed. (So, the constructor of unique_lock calls lock() on the mutex; and the destructor calls unlock()).

    Missing the point. Why does std::mutex not unlock itself (as necessary) in its own destructor?

    @cvi said:

    But, unique_lock is not a wrapper around a different class.

    Didn't you just explain how it's a wrapper around std::mutex?



  • This post is deleted!


  • @cvi said:

    There's no make_shared for arrays.

    Huh, now that is a curious omission. Never noticed that was missing. I wonder why it didn't make it into the standard. Boost seems to have one, though, so the standard shouldn't be far behind.


  • Considered Harmful

    @LaoC said:
    Of course you can just insert a q := nil to silence the compiler and simulate the more common case that someone passes you a NULL by accident. freemem doesn't care.
    In which case you have a completely different problem: assignment to a null pointer without checking your arguments. That problem exists in every language where you have reference types that are nullable by default; there's nothing particularly noteworthy about Pascal in that particular aspect of its type system. (And if you `freemem` a `nil`, nothing happens.)
    But it segfaults when you pass it something you didn't allocate. Which you can.
    @LaoC said:
    Don't pretend C didn't work. It's just hard to make it safe.
    No. People accomplish "hard" all the time. C safety is *beyond* hard. It's a problem *so inhumanly hard* that even people with decades of experience keep making the same mistakes. See for example the Heartbleed bug. The guy who screwed that up is obviously a very experienced developer who knows what he's doing, but to err is human. The problem is that in C it's very easy to do the wrong thing and get away with it, and that can't be fixed because backwards compatibility.
    Bullshit. Seggelmann was a graduate student at the time and would have had to have started C as a toddler to claim "decades" of experience. The protocol design is awful. To quote his thesis:
    The format of the new messages can basically be arbitrary, because for keepalive no further information than the message type is necessary. However, to make the extension as versatile as possible, an arbitrary payload and a random padding is preferred, illustrated in Figure 7.1
    Translation: I have no idea what the fuck this could possibly be useful for but I'll add the code for it anyway on the off chance that some day someone could need it. As opposed to, say, putting a version number somewhere for future extension. And to top it off, he lets the client supply the payload length, something people even complained about in prior discussion on the IETF mailing list. Doesn't look very experienced to me.


  • @asdf said:

    I'll let you know when I find my old test code again. I honestly don't remember what the edge case was, but I remember finding an edge case a while ago and playing around with test code to reproduce it.

    I'm curious as to what these are.

    using / try-with-resources only work with statements that result in objects that implement the IDisposable and AutoCloseable interfaces in their respective languages. Anything else should result in a compiler error.


  • Winner of the 2016 Presidential Election

    @cvi said:

    the make_unique<>() emulation that I'm using doesn't implement the array version. 😦

    #if __cplusplus == 201103L
    
    #include <cstddef>
    #include <memory>
    #include <type_traits>
    #include <utility>
    
    namespace std {
    
    template<class T> struct _Unique_if {
        typedef unique_ptr<T> _Single_object;
    };
    
    template<class T> struct _Unique_if<T[]> {
        typedef unique_ptr<T[]> _Unknown_bound;
    };
    
    template<class T, size_t N> struct _Unique_if<T[N]> {
        typedef void _Known_bound;
    };
    
    template<class T, class... Args>
    typename _Unique_if<T>::_Single_object
    make_unique(Args&&... args) {
        return unique_ptr<T>(new T(std::forward<Args>(args)...));
    }
    
    template<class T>
    typename _Unique_if<T>::_Unknown_bound
    make_unique(size_t n) {
        typedef typename remove_extent<T>::type U;
        return unique_ptr<T>(new U[n]());
    }
    
    template<class T, class... Args>
    typename _Unique_if<T>::_Known_bound
    make_unique(Args&&...) = delete;
    
    }
    
    #endif /* __cplusplus == 201103L */
    

    You're welcome.



  • It's probably coming with C++17 (unless they forget all about it again). Yeah, it's an annoying omission, (somewhat similar to the omission of make_unique() in C++11).



  • Already put that on my TODO list. πŸ˜„


  • Winner of the 2016 Presidential Election

    @Mason_Wheeler said:

    implementing a high-level construct on top of a lower-level construct and then not providing access to the more fundamental construct, forcing people who need it to inefficiently reimplement it on top of the high-level construct.

    Highlighted the questionable parts. Why would people need to access the destructor directly, and what do they need to reimplement inefficiently?


  • Winner of the 2016 Presidential Election

    @Mason_Wheeler said:

    > And I especially don't understand why you think peppering your code with try ... finally (that you have to remember to put in every single scope where you use a resource) is any better.

    It becomes automatic very quickly when you actually try it. (No pun intended.)

    No, that's my whole point: It's not automatic, you need to remember to use it. So it's not any better than manual memory/resource management.



  • @Mason_Wheeler said:

    If your code is freeing pointers that someone else passed in from the outside, that's the problem right there.

    miniz is a library. I extracted the file because my application needed access to its contents. The data gets passed around the application. The application may evolve over time and who owns that memory may change as different devs modify the app and add features that may require access to that memory. Through all that, they all have to remember that the memory was obtained from a library that may have a special allocator.

    Or you use a smart pointer and don't need to worry.

    @Mason_Wheeler said:

    So to replace a simple idiom that takes 4 lines of code, you propose one that takes 10 to set up, (off in some other place,) and 5 lines every time you want to invoke it--and writes the logic out of order to boot--and call that simpler and easier to read?
    It's a retarded, backwards idiom. It makes sense that it should be written backwards. It helps to remind you not to use retarded, backwards idioms.

    Your preference is manual resource management. In 2016.

    @Mason_Wheeler said:

    It becomes automatic very quickly when you actually try it. (No pun intended.)
    You know what's more automatic? A machine doing it automatically for you.

    @Mason_Wheeler said:

    Missing the point. Why does std::mutex not unlock itself (as necessary) in its own destructor?

    Bcause the mutex is a lock, and the lock isn't destroyed until the shared object is destroyed. You don't create a new lock every time you want to access the mutexed resource. You look at the existing lock, and ask "can I take this?" If it's unlocked, you lock it and the next person that comes looking sees the pre-existing locked mutex and knows they have to wait.

    So you use the mutex to manage the "locked/unlocked" state, and the unique_lock to handle the responsibility of locking and unlocking.


  • Winner of the 2016 Presidential Election

    @powerlord said:

    I'm curious as to what these are.

    using / try-with-resources only work with statements that result in objects that implement the IDisposable and AutoCloseable interfaces in their respective languages. Anything else should result in a compiler error.

    Maybe I'm misremembering and I was just confused by the fact that you need to put another try-with in the factory method itself back then. Anyway, try-with is not as convenient as RAII, and certainly not "automatic", because you still need to remember to put the try in every single scope in which you use the resource.



  • @asdf said:

    @powerlord said:
    I'm curious as to what these are.

    using / try-with-resources only work with statements that result in objects that implement the IDisposable and AutoCloseable interfaces in their respective languages. Anything else should result in a compiler error.

    Maybe I'm misremembering and I was just confused by the fact that you need to put another try-with in the factory method itself back then. Anyway, try-with is not as convenient as RAII, because you still need to remember to put the try in every single scope in which you use the resource.

    True, but you also only use try-with-resources with external resources... GUI stuff, file stuff, and database stuff mainly.



  • @Mason_Wheeler said:

    @cvi said:
    Now, instead of doing the locking manually (which typically also has problems with being exception unsafe), you can use std::unique_lock. So, creating a unique lock (std::unique_lock myLock(myMutex);) will acquire the lock on the mutex (myMutex), and release it automatically when the lock myLock is destroyed. (So, the constructor of unique_lock calls lock() on the mutex; and the destructor calls unlock()).

    Missing the point. Why does std::mutex not unlock itself (as necessary) in its own destructor?

    @cvi said:

    But, unique_lock is not a wrapper around a different class.

    Didn't you just explain how it's a wrapper around std::mutex?

    The one missing the point of std::unique_lock is YOU.

    It is NOT a wrapper around std::mutex. If you want to call it a wrapper, it is a wrapper around locking and unlocking a mutex. See the example, which is probably totally alien to the true syntax of std::mutex and std::unique_lock...
    [code]
    // Somewhere
    std::mutex protector;

    // Somewhere else
    {
    std::unique_lock locker( protector ); // Locks the mutex HERE.

    // Some code that needs to be mutex-protected.
    //
    // OK, done now.
    

    } // destructor of locker called here, unlocks the mutex
    [/code]

    The mutex itself is independent of the std::unique_lock variable.

    And the correct action for a locked mutex when it is destructed is to assert in debug builds and explode in flames in release builds.


  • area_pol

    @PJH said:

    Now - do I need to use delete or delete[] on this pointer...?

    Neither, use a std::vector as a field in your class and it will deallocate automatically.



  • @LaoC said:

    But it segfaults when you pass it something you didn't allocate. Which you can.

    So are we talking about an unintiialized variable or a variable that's been initialized to nil? Make up your mind, because those are two completely different problems. (And if you try to pass an uninitialized variable to a parameter that's not explicitly designated as out, the compiler will warn you on that too.)

    @LaoC said:

    And to top it off, he lets the client supply the payload length, something people even complained about in prior discussion on the IETF mailing list. Doesn't look very experienced to me.

    if people knew ahead of time that the client supplying the payload length could cause the Heartbleed bug, why did it take so long to discover the Heartbleed bug?

    @asdf said:

    Highlighted the questionable parts. Why would people need to access the destructor directly, and what do they need to reimplement inefficiently?

    "The destructor" isn't the more fundamental construct here; the concept of a try/finally block is. RAII is implemented by the compiler silently creating code functionally identical to the following for each applicable scope:

    try
    {
       scope body here
    }
    finally
    {
       ~raii'd_object();
    }
    

    But you can't write your own try/finally blocks for purposes other than object destruction; if you want that you have to create an object whose only point in life is to be destroyed by RAII in order for its destructor to do what you actually wanted done without any of this RAII nonsense getting in the way. A textbook case of abstraction inversion.

    @asdf said:

    No, that's my whole point: It's not automatic, you need to remember to use it. So it's not any better than manual memory/resource management.

    And in the provided example you have to remember this big ugly template with a lambda inside, so how is that any better?

    @Kian said:

    miniz is a library. I extracted the file because my application needed access to its contents

    Yes, I get that part.

    @Kian said:

    The data gets passed around the application.

    Directly? In its raw form, without first being parsed in any way? That's where this scenario starts looking unlikely.

    @Kian said:

    It's a retarded, backwards idiom. It makes sense that it should be written backwards. It helps to remind you not to use retarded, backwards idioms.

    Only when you're forced to reimplement it in an retarded, backwards abstraction inversion. In its natural state it's very simple and very useful.

    @Kian said:

    Your preference is manual resource management. In 2016.

    Gah! It's not about resource management. try/finally is not about resource management. Your brain has been so poisoned by RAII forcing you to reframe everything in terms of resource management that it's like that's literally all you can see! try/finally is about guaranteed, exception-safe, reversible state changes, for resource management or any other use case.

    @Kian said:

    Bcause the mutex is a lock, and the lock isn't destroyed until the shared object is destroyed. You don't create a new lock every time you want to access the mutexed resource. You look at the existing lock, and ask "can I take this?" If it's unlocked, you lock it and the next person that comes looking sees the pre-existing locked mutex and knows they have to wait.

    Sure, but what does that have to do with being able to handle destroy-on-unlock by itself or not?


  • Discourse touched me in a no-no place

    @Adynathos said:

    @PJH said:
    Now - do I need to use delete or delete[] on this pointer...?

    Neither, use a std::vector as a field in your class and it will deallocate automatically.

    With all these perfect solutions to avoid the problems, would someone like to tell me why malloc(), new and new[] are still things in C+11, since they got rid of other things (like the old meaning of auto)? 🍹

    Any answers that involve keeping them there will be directed back to my original question.


  • Considered Harmful

    @asdf said:

    @LaoC said:
    Either you propose that allocated objects should carry type information that automagically associates them with their proper deallocation function, which is incompatible with the requirements for programming bare metal

    Why is std::unique_ptr<> incompatible with the requirements for programming bare metal?

    I've never used that feature; when I last wrote C++, auto_ptr was all there was and it was crap. Now as far as I read, the "unique" part means there can only ever be one copy of that pointer so it's always clear who owns it and when it needs to get destroyed. If I'm getting that correctly, that doesn't work with hardware registers. Say you have a network buffer pointed to by one of these. You enter the pointer into some DMA controller register after shoving it through an MMU translation to find the physical address. Now you have two different bit patterns (virtual and physical address) referring to the same object (can you even do that? Basically the pointer should be opaque, otherwise it would be trivial to sneak a copy past the compiler by just doing some bit-fiddling), owned by independent bits of hardware. You can't destroy the buffer yet though because that would free it and it could be overwritten before the DMA controller got a chance to read it. Then you get an interrupt, and sometimes it's a spurious one coming from different hardware, so you have to check whether the value in the register has changed. Then some hardware is weird enough that after an interrupt you have to rewrite values to restart the DMA in case only part of the data you wanted was transferred. So you have to read the value which is actually a pointer, but you can't make a unique_ptr from it because that would imply calling its destructor at some point, and it actually points to a portion of the data belonging to the first unique_ptr which is responsible for destroying it all.



  • @Adynathos said:

    @PJH said:
    Now - do I need to use delete or delete[] on this pointer...?

    Neither, use a std::vector as a field in your class and it will deallocate automatically.

    And generalising slightly: C++ gives you a wide range of tools for eliminating raw pointers, and this range has progressively increased as newer versions of the language and its libraries have been released.

    That said, my personal opinion is that std::shared_ptr inherits a deeply ugly flaw from its Boost predecessor. Both of them (the last time I looked) have a nice quiet method for accessing the raw pointer, T *std::shared_ptr<T>::get(). The flaw would be much less ugly if the method was T *std::shared_ptr<T>::give_me_the_raw_pointer_so_I_can_cause_bugs(), or even T *std::shared_ptr<T>::operator ->(). Calling either of those is a real clue to code reviewers that you are doing something you should not be doing.


  • Winner of the 2016 Presidential Election

    @Mason_Wheeler said:

    But you can't write your own try/finally blocks for purposes other than object destruction; if you want that you have to create an object whose only point in life is to be destroyed by RAII in order for its destructor to do what you actually wanted done without any of this RAII nonsense getting in the way.

    *sigh*

    • RAII: You have to wrap stuff in an object (most of the time, you just use one from the standard library)
    • try/finally: You have to add an additional construct when you use resources that you need to clean up and you have to write the cleanup code yourself.

    The amount of boilerplate code is roughly the same in both cases (or more in the try/finally case if you need to pass the resource through different scopes). The difference is that RAII ensures that your code is leak-free, while try/finally can easily be forgotten. So I still disagree: RAII doesn't make a fundamental concept more inefficient or complicated to implement.

    If you really think RAII makes real-world tasks more complicated or inefficient, I suspect you've never actually used C++11.


  • Considered Harmful

    @Mason_Wheeler said:

    @LaoC said:
    But it segfaults when you pass it something you didn't allocate. Which you can.

    So are we talking about an unintiialized variable or a variable that's been initialized to nil? Make up your mind, because those are two completely different problems.
    What about crashing the way I crashed there, by giving freemem() a pointer to a static string?

    @LaoC said:
    And to top it off, he lets the client supply the payload length, something people even complained about in prior discussion on the IETF mailing list. Doesn't look very experienced to me.
    if people knew ahead of time that the client supplying the payload length could cause the Heartbleed bug, why did it take so long to discover the Heartbleed bug?
    They didn't know it could cause the Heartbleed bug, they just hinted that this is bad protocol design. I suppose nobody cared enough to look closely after their comments were ignored.


  • @PJH said:

    @Adynathos said:
    @PJH said:
    Now - do I need to use delete or delete[] on this pointer...?

    Neither, use a std::vector as a field in your class and it will deallocate automatically.

    With all these perfect solutions to avoid the problems, would someone like to tell me why malloc(), new and new[] are still things in C+11, since they got rid of other things (like the old meaning of auto)? 🍹

    Any answers that involve keeping them there will be directed back to my original question.


    Let's see:

    • malloc still exists because of the occasional need to interface with C code that only understands malloc and free. (This ignores arguments that if I allocate the memory, I should be responsible for freeing it, which is usually but not exclusively the case.)
    • new and new[] exist because someone, somewhere needs to be able to actually allocate the memory used by the abstractions, and in some cases they need merely to allocate it (and not do trickery like std;:vector::reserve() and friends)

    But in general, once you step outside those areas, you should avoid using them.



  • @asdf said:

    The difference is that RAII ensures that your code is leak-free, while try/finally can easily be forgotten.

    Sure, and there's no way to forget the big, complicated, out-of-order finally-lambda-template monstrosity.

    @LaoC said:

    What about crashing the way I crashed there, by giving freemem() a pointer to a static string?

    Oh, is that how you were trying to crash? Why don't you try actually doing that, then (hint: you want to call freemem() on p and leave q out of your sample code entirely, so it doesn't crash on q before even reaching the freemem() call) and see what happens?

    *gasp* It doesn't crash!


  • Winner of the 2016 Presidential Election

    @LaoC said:

    when I last wrote C++, auto_ptr was all there was and it was crap

    😷

    So your point is that C++98 was not much better than C. I agree. Take a look at modern C++ sometime.

    @LaoC said:

    Say you have a network buffer pointed to by one of these. You enter the pointer into some DMA controller register after shoving it through an MMU translation to find the physical address. [...]

    Well, if you do stuff like that, you have to use raw pointers, of course. Still doesn't mean C is any better than C++ for implementing something like that.

    @Steve_The_Cynic said:

    Both of them (the last time I looked) have a nice quiet method for accessing the raw pointer, T *std::shared_ptr<T>::get().

    That's intentional and not a problem. If you use C++11 correctly, you know that a raw pointer means "I can use this object temporarily, but I do not own it".


  • Winner of the 2016 Presidential Election

    @Mason_Wheeler said:

    finally-lambda-template monstrosity

    Have you ever seen C++11 code? I'm seriously starting to doubt that. All you need is provided by the STL.


Log in to reply