3 ms·
I'd be interested to hear why you say that RAII cannot be idiomatic. With `std::unique_ptr` and `std::shared_ptr` for handling pointers, `std::vector` for hand
by MereInterest 10y ago
I'd be interested to hear why you say that RAII cannot be idiomatic. With `std::unique_ptr` and `std::shared_ptr` for handling pointers, `std::vector` for handling data arrays, `std::string` for handling char arrays, I would argue that modern C++ is very heavily RAII. At this point, if I have a class that cannot be declared on the stack, with cleanup handled by RAII, I consider it to be a broken class.
Good point on limitations vs simplicity. I think that there are different forms of complexity. Some complexity is necessary, due to the nature of the underlying problem. Other complexity is incidental, being due to poor implementations. As an example of essential complexity, C++ has different declarations for stack-based variables and heap-based variables, while Java does not. This is because Java does not allow classes to be declared on the heap, and so it does not need an extra form of declaration. On the other hand, C++ definitely has lots of incidental complexity as well, mostly due to its long history. Having four different constructs for loops is incidental complexity.
I agree that the idioms are what make Java programs be complicated, rather than the language itself. What I'm not sure on is how much those idioms are needed to overcome limitations in the language, and how much they are unnecessary parts of the culture.
Oh, absolutely on Lisp. Lisp/Scheme are amazingly simple, and amazingly powerful. The one downside that I've found is that it doesn't correspond to the hardware as much. Much of C++'s complexity stems from trying to provide as many features as possible while still compiling down to reasonably fast code.
- junke 10y ago> The one downside that I've found is that it doesn't correspond to the hardware as much. The hardware keeps changing. What Lisp provides is a way to access low-level details when necessary, but with a default mode of operation that is managed (developer-friendly): GC, safety checks, arbitrary precision integers, etc. Like for all languages, a compiler can be told to try more optimizations if you want, and if you are ready to spend a little more work on it. In some implementations, you can extend the primitives known the compiler to emit better assembly code (e.g. https://www.pvk.ca/Blog/2014/08/16/how-to-define-new-intrinsics-in-sbcl/ https://www.pvk.ca/Blog/2014/08/16/how-to-define-new-intrins...).
- qwertyuiop924 10y agoWell then, that's your problem: C++ is trying to be both low-level and high level at the same time. This isn't a good idea, IMHO. What I mean is that while some amount of RAII is a common idiom in C++, It's rare for a program to use RAII fully, as many C++ idioms conflict with it. Mind, I'm not an expert, so I might be wrong, but that was my understanding. AFAICT, if RAII was used all the time, idomatic C++ would look a lot more like Rust.
- pjmlp 10y ago> Well then, that's your problem: C++ is trying to be both low-level and high level at the same time. This isn't a good idea, IMHO. It is a very good idea and C++ isn't alone there. Professional Basic dialects, Turbo Pascal, Delphi, Modula-2, Modula-2+, Modula-3, Ada, D, Rust, Mesa/Cedar all share this idea that you can program at both levels, depending on the needs of the use case.
- qwertyuiop924 10y agoC++ has less distinction though, and it's so huge that even Bjarne can't keep the whole language in his head. The net result is that many people use the low-level parts of the language for high-level work and vice-versa.
- pjmlp 10y agoThe same applies to the languages I listed, given the amount of years they have. C++ main issue has always been C's compatibility and the C subculture. The copy-paste compatibility was necessary to bring C developers over the fence to C++, with minimal changes to their tooling. So C++ inherited all the flaws and UB from C. Then many of those kept using C++ as plain C with Classes, which influences many of the design decisions regarding language semantics, specially given backwards compatibility as no one wants to repeat Python's error. Having said this, although I enjoy playing with template metaprogramming, I hope never have to deal with SFINAE, declpspec and function return arrow syntax in production.
- pjmlp 10y agoThe main issue with RAII is that you can only do properly if as you say, the classes were designed to be stack allocated. Also that no one just placed such class in the the heap, and it was missed, because no one is actually doing code reviews or making use of static analysis. Another issue with best practices and ownership are binary libraries. There isn't any sort of control one can have over them, so they are the place where RAII and ownership just goes out of the window. In any case this is much worse in C than C++, because at least C++ does offer some language tools to deal with it, however there is no rescue from developers using C with a C++ compiler. > Oh, absolutely on Lisp. Lisp/Scheme are amazingly simple, and amazingly powerful. The one downside that I've found is that it doesn't correspond to the hardware as much. Actually it can also be an upside, as you see with the adoption of FP patterns in mainstream languages, including C++. One of the themes at CppCon was exactly hardware heterogeneity and possible C++ abstractions to take advantage of it, while keeping the algorithms high level.
- qwertyuiop924 10y agoPretty much exactly the point I was trying to make about RAII. Thanks for explaining better than I did :-).
- MereInterest 10y agoI feel that the solution to both classes designed for heap allocation and binary libraries is the same: making a decent wrapper around them that obeys RAII. In many cases, this is just requires using `unique_ptr` with a custom deleter. auto deleter = [](unsafe_type* t) { cleanup_unsafe(t); } std::unique_ptr<unsafe_type, decltype(deleter)> safe(make_unsafe(), deleter); If this is used in many places, you can make a very easy wrapper class to handle it. class safe_wrapper { public: safe_wrapper() : unsafe(make_unsafe()) { } ~safe_wrapper() { cleanup_unsafe(); } unsafe_type* operator->() { return unsafe; } private: unsafe_type* unsafe; }; (Note that the example there is only for use within a single function. If the safe_wrapper is to be returned from a function, then the copy/move operators should be defined as needed.) Certainly, when passing ownership back to the binary library, you are relying on it to correctly handle ownership. But so long as the ownership is in one's own code, you can easily add the type safety. Regarding a stack-based class being accidentally placed on the heap, you can't prevent all errors, you can only make it less likely. I'd argue that it is easier to accidentally forget to call a cleanup function than it is to accidentally place something on the heap. With C++11 and up, any calls to "new" should be regarded with deep suspicion. True on the FP patterns moving in, and I love them. `std::function` is a joy to work with, especially as compared to either C-style function pointers or inheritance from a "callable" type.