I think I watched all the talks from Going Native 2012, and I highly recommend them [1]. I expect 2013's will be just as good, though I won't have time to watch any for a while. Here are the others: http://channel9.msdn.com/Events/GoingNative/2013?sort=sequen...
really interesting to see classes as ressource manager above all, instead of concept incarnation.
I was almost convinced to go back to C++ until the slide with auto range and all, combining new features of C++, at which point i remembered why i didn't want ro have a look at C++ code again.
but hey, i'm not a system programmer so i can afford it :)
RAII ("classes as resource manager") is one of the nice concepts in C++. My favourite is certainly const correctness.
And then there's tons of things that creep me out a bit. Being a multi paradigm language sounds good on paper, but it seems to cause a lot of accidental complexity. The best way to stay sane is probably to pick a certain subset of C++ for your project and stick to that, that's what I tend to do.
Then again, that's not uncommon in simpler languages either. "JavaScript, The Good Parts", lint and all that. Code accessibility matters IMO.
Can't say anything about Haskell, but what Scala has is less flexible then const correctness in C++ IIRC. You can't have an immutable reference to a otherwise mutable object and then call non-mutating functions on it. It's either fully immutable or fully mutable. CMIIW by all means.
The point of the example is that this is how NOT to do it in C++.
There are two better options:
void f(int n, int x) {
Gadget p(n); // Stack allocated
// ...
if (x < 100) throw new Exception("Weird!); // no leak
if (x < 200) return;
// ...
}
Or, if it really has to be a pointer:
void f(int n, int x) {
std::unique_ptr<Gadget> p = new Gadget(n); // Smart pointer
// ...
if (x < 100) throw new Exception("Weird!); // no leak
if (x < 200) return;
// ...
}
Both will be automatically freed as soon as the scope is exited.
Well, what's easy in Java here is that you're effectively always working with shared objects. That's easy to use, but in my experience quite error prone, I can't count how many memory leaks I fixed in Java/JS code. YMMV.
In C++, you have various ways of referencing and passing objects, so you need to be aware of the lifetime and ownership of objects. It's arguably harder, and unfortunately, the C heritage makes it a lot harder than it needs to be :(
An annoying fly in this ointment is cases where objects assume the responsibility for calling Dispose on caller-provided disposable objects. While it should always be okay to "double-dispose", this is only a guideline, and can't be enforced by the language, so, to maximize future compatibility with classes outside your control, you should probably defensively avoid calling Dispose on an object that will have already been disposed by other means.
And try/finally assigns the cleanup responsibility to the caller, not the callee, which just adds boilerplate and mental burden. C++ does not need a finally block due to RAII. The using block (and "try-with-resource" in Java 7) is a poor man's RAII emulation.
Anyway, what if you need to share non-memory resources? Suddenly you cannot depend on the garbage collector, you cannot use try/finally, you cannot use using or try-with-resource - you need to handle the situation just like in C++, except you're given fewer tools to do it - and a poorer understanding of the situation if you've learned that you don't need to do manual resource management due to the garbage collector.
True, but 90% of resources is memory. Java makes 90% of resources much easier to handle and 10% only little harder (like always use the try-catch-finally or try-with-resources).
They are poor solutions compared to RAII: leaky abstractions which are non-composable and error prone. Unfortunately, something like C# using is probably the best you can do in a language without clear ownership semantics like C++.
I've done a limited amount of C++ many many years ago before I even knew what garbage collection was and I keep thinking of revisiting it, but honestly in my line of work ("Enterprise") I don't need the mental overhead of dealing with things such as pointers and memory allocation.
Perhaps, my view is outdated, but I get the impression that everything in C/C++ is just a little thorny when compared to other slightly more high-level languages, such as namespaces, package management, list comprehensions, library compatibilities, type strictness etc.
I would like to be wrong about that though... I wish I had a little more motivation to spend some real time with C++ (or perhaps even C).
In OOP its recommended to use pointers as class members, managed by class methods. What you show is a "wild" allocation that doesn't really belong to no one and thus not really managed. You're right, pointers can be misused, but what can't be? Think of infinite loops or some similar very bad algorithm choice if nothing else. No amount of language design will fix bad programming.
I don't think OOP means what you think it means. There's nothing in Object Oriented Programming paradigms that specifies what you should declare class members as. There are more important components to that decision such as what language you're using.
OOP means you design your thing as objects, with each object taking care of its own stuff. You don't have to use pointers, you may just use other objects as members, as you do in many other languages. Pointers just give you additional power and flexibility and if they are used in a object oriented design, its safe to assume that they should be managed by the owner object, like any other object's internal stuff.
Bjarne's advice also applies to pointers wrapped as class members. There are just too many ways you can mess things up inside the class when dealing with raw pointers, examples including:
- partial construction: having to deal with already allocated pointers in case of errors during the construction
- copy construction: who owns the resources? (Alternatively, you'll need to remember to disable the copy constructor explicitly.)
- copy assignment: likewise
Doing the Right Thing is just so much easier when you wrap those member objects into e.g. `std::unique_ptr<T>`. That'll disable copy construction and assignment, which means that you'll need to think of that separately if it's needed.
These are problems that pop out from a bad design choices. One of the first things I remember from OOP learning is that constructors should be as safe as they can be, which means no allocations, partial or not. Of course it is tempting to misuse the language features out of personal commodity and blame someone else. I think we can agree that a few design changes would solve the ambiguous cases. Also, for the "remembering" problem, we can inherit from a base class having all the needed restrictions in place. The std's "something_ptr" gimmicks may sound like a good compromise at first, but just like the garbage collector, these will make you lazy and neglectful over time. It's good for those to exist though.
> constructors should be as safe as they can be, which means no allocations, partial or not.
Can you expand upon what you mean by "safe" here?
If you mean "unable to fail", I vehemently disagree. Constructors should validate what is passed into them and fail if that is invalid. The alternative is to construct a zombie object that can't actually be used. Objects like this subvert the type system and lead to lots of unnecessary "if object.is_valid()" checks all over code that uses them.
"safe" like nullifying pointers and dummy-initialization for objects. This kind of initialization has a small footprint on performance, but it was made optional nevertheless, due to language's performance aims (you can skip them if you don't need that). You can make a little bit more complex operations in constructors that involve data validation, but it should only default to blank initialization if something doesn't pass the checks. The initialization that can fail (and the failure must be taken in account) should be done outside constructors. If an object is or isn't considered valid after a dummy initialization is a design decision, and likewise an adjustment in design can avoid excessive "if (object.is_valid())" kind of checks.
>That'll disable copy construction and assignment, which means that you'll need to think of that separately if it's needed.
If you have a pointer in a class that is exactly what you want to happen in most cases. Then if you want copies you "have to" define your own copy constructor and assignment operator that copy the object pointed to rather than just copying the pointer.
If you've ever wondered what's the deal with C++ and why it's being used in 2013, you might wanna watch this. Stroustrup isn't a very flashy speaker, but he says some incredibly insightful things.
C++'s C heritage makes it hard to master and also causes countless misconceptions. Even if you never want to use C++, it's worth looking into some of the unique concepts that, sadly, didn't catch on in other languages so far. (My personal favourites are const correctness and RAII.)
Do watch the talk, the first few minutes might already be mind blowing for anyone who thought C and C++ are basically the same language.
There are also some things that are powerful but I'm not quite sure if I really like them. For instance, he promotes the use of handles instead of pointers. He goes through these variants:
Gadget* p = new Gadget(n); // not exception safe
shared_ptr<Gadget> p{new Gadget(n)}; //exception safe
unique_ptr<Gadget> p{new Gadget(n)}; //exception safe and less wasteful than shared_ptr if local
Gadget g{n}; //his preferred solution
My problem with his preferred solution is that I cannot know that Gadget is really a handle to a shared Gadget and not a big fat Gadget value on the stack. The only way of knowing that is to look at the implementation or the documentation.
So if g is returned from a function or passed to a function by value, I don't know whether or not a deep copy is made. If I manipulate that Gadget, am I manipulating a shared object affecting others or is this my private copy?
Qt uses that pattern throughout, and because it is used for everything in Qt, you know that you're dealing with handles. But the C++ standard library doesn't do it that way. Almost none of the classes in the standard library are handles.
If I see a pointer, shared or otherwise, I know I'm not dealing with a deep copy. I know someone somewhere else might point to the same object.
> My problem with his preferred solution is that I cannot know that Gadget is really a handle to a shared Gadget and not a big fat Gadget value on the stack. The only way of knowing that is to look at the implementation or the documentation.
I'd say you do not _need_ to know. If Gadget contains tons of data, it's wiser for it to just put that on the heap, internally. Like std::vector does. As long as Gadget handles (or forbids) copying, assigment etc. (rule of three), that's fine.
If you do have a huge performance issue because you're using a class that, unwisely, puts tons of data on the stack, you can use a unique_ptr instead. Kind of an edge case in my experience though.
I disagree. You have to be absolutely certain at all times whether you're holding on to a reference to a shared data structure or you have your own copy, unless the object is immutable. This is not just a performance thing.
You mean that it's not clear if copying the handle will copy the data or just create another reference? At least in the STL that's kinda clear: vector will copy its elements (if they are pointers, it'll copy the pointers. if they are values it'll copy all the data), shared_ptr will copy the handle pointing to the same shared object, unique_ptr is not copyable and so on.
But agreed, it's opaque when working with third party code. Gotta figure out the conventions.
>You mean that it's not clear if copying the handle will copy the data or just create another reference?
Exactly. In the STL, if it looks like a value copy it is a value copy, but the handle pattern that Stroustrup prefers gives you no clue whatsoever as to what's going to happen. You have to figure it out one class at a time.
I'd also say that using any class without reference to either its documentation or its implementation is a terrible idea in the first place, because who cares where it stores data if you're not using it properly?
I can't count the number of bugs I've fixed due to programmers (myself, sadly, included) using IDE code completion features as a substitute for reading documentation.
Somehow I don't find this to be a problem in my code. Yes, when using someone else's library you need to know how they handle ownership. But this is basically a problem inherited from C.
For example, in the code you posted all the pointer-like handles are named 'p'. In my experience, if we consistently pursue minimal scope and minimal lifetime for our objects, then longer-lived objects are oddballs and we use the type of the handle to help describe its ownership strategy.
I agree that this is an issue with third party libraries, not with my own code. But C++ libraries can be a real horror. Every single one of them comes with its own peculiar memory management idea. Recently I started to use rapidjson. It's insanely fast but that's not the only insanity. It basically screams for memory management bugs.
Well, at this point it's more like C++'s C++ heritage.
> const correctness
It certainly exists in plenty of other languages, but in a non-broken way. C++'s const correctness model is mostly broken, by the way [1]. D's model is a bit more sane [2]. Languages like Haskell or Clojure (AFAIK) make immutability a default. This kind of functional style is a good thing for C++ developers overall, as emphasized by John Carmack [3].
> It certainly exists in plenty of other languages, but in a non-broken way. C++'s const correctness model is mostly broken, by the way [1]. D's model is a bit more sane [2]. Languages like Haskell or Clojure (AFAIK) make immutability a default.
What in particular is broken about const correctness in C++? That things are not const by default? I agree, they should be, but I wouldn't really call that broken. I put const wherever I can, just like Carmack. So I sort of pretend it's the default :)
I don't think it makes sense to compare const correctness in C++ to immutability in Clojure. In Clojure, you don't have objects that encapsulate internal state. const in C++ allows you to say which methods do change internal state and which don't. If you pass a const reference, the receiver can only call const methods. I just learned from you that D has this, wasn't aware of any other OOP language that has it. Not even Scala :(
> That's because most other languages are garbage collected. (Not that RAII is a bad thing, it's just not needed.)
RAII is about more than just memory, it's about any kind of resource. That includes stuff like file handles, Java's try {} finally {} is IMO a weird workaround. In C++, stream's can just close themselves when they go out of scope.
> What in particular is broken about const correctness in C++?
The lack of transitivity for one. Const pointers shouldn't be able to modify the pointed objects (that's the default in the D language). The fact that working with non-const-correct libraries is a nightmare (prepare to const_cast a lot!). There are plenty of other things that make this a real PITA to work with on sizable projects with legacy or outside code.
> RAII is about more than just memory, it's about any kind of resource.
That's true, yeah, though most of the time it's about memory (with smart pointers). But you're absolutely right on that point.
As far as I know, Common Lisp users have some kind of RAII with macros, but I'm not familiar enough to compare it to C++. Of course, D also has support for this idiom.
> The lack of transitivity for one. Const pointers shouldn't be able to modify the pointed objects
Are they? I either misunderstand or disagree. The syntax gets a bit silly with pointers, because you can have const pointers and pointers to const (and const pointers to const of course):
const Foo* foo; // Cannot change *foo, but can change the pointer (e.g. foo = 0)
Foo* const foo; // Can change *foo, but cannot change the pointer
const Foo* const foo; // Cannot change *foo or the pointer
> The fact that working with non-const-correct libraries is a nightmare (prepare to const_cast a lot!).
Agreed!
> As far as I know, Common Lisp users have some kind of RAII with macros, but I'm not familiar enough to compare it to C++.
In my experience, the kind of problem RAII is useful for is far less common in Lisps (can only really speak for Clojure). One tends to not encapsulate resource management in any way, so handling it explicitly in the caller is fine. And you can use macros to do something when the scope is excited.
So in Clojure e.g., you can use with-open to do something with a file:
As somebody who attended this conference, and hadn't had much exposure to C++11 features outside of 'auto', my personal biggest takeaway from almost every talk was this: Stop passing your sink variables as const refs.
You're hamstringing the compiler into always copying that string instead of being able to use the new move semantics, because it can't mess with the guts of a const reference. Instead, do the previously unspeakable evil of passing by value and then moving, e.g.
This lets the compiler know that if string has a move constructor, and is an rvalue, it can just move the guts into place instead of performing the copy, since the variable is 'sunk' into the new location. Huge wins all around.
I understand that, but it's clearly not used in the body. I was hoping it would be a legal and implemented optimization.
move calls strike me as ugly and kinda dangerous. What if you do wind up wanting to refer to s in the body later on? It'd be nice if the compiler just handled it.
Once things have a name and you can take their address they are no longer an rvalue. It would require a much more sophisticated type system to do this automatically (when you pass a reference to another function, does it capture it?). It would also be non-obvious when you were moving. Think of Java style escape analysis vs C++ stack allocation (implicit vs explicit). There is one case where the compiler will do this though, when you return a stack allocated value from a function, it is implicitly moved.
The argument here is that in the body of the constructor the developer probably doesn't use s, so a trivial analysis of the function can say "oh, it doesn't matter if I destroy s, so in the one place where it is used I'll just move it". It doesn't then matter if it is obvious it happens or not, because the semantics of the program wouldn't be maintained (and honestly, I'd argue it is never quite obvious when what happens in different contexts given the large number of rules involved: you kind of just have to have some trust).
There are a number of considerations because the compiler can't prove it in general without augmenting the type system or doing whole-program analysis.
Do you allow, but not require, the compiler to do this substitution whenever it can prove a value is never used again? This is unreliable across build options and compilers so it would be unwise to depend on it. There is an opportunity here for a tool that could identify places where you could insert move, perhaps even a -W option for the trivial cases such as above, but I am not convinced the language should allow the compiler to do this.
Do you make it mandatory and add more special cases for the compiler to have to implement? This would require the compiler to track references to make sure they don't get passed to some other function. It would need someone to codify the special cases in the standard and this might be very difficult. It would also be fragile, there would be cases where implicit move used to kick in but some added function call inhibits it even though a move is still the right thing to do.
You seem to be assuming that optimizations are only of value if they can be depended on happening consistently as a defined property of the language on every single platform for which there exists a compiler: in practice, compilers perform numerous optimizations (such as static branch prediction) that only work probabilistically even for the case of a specific version of a specific compiler on a specific operating system; I know of very few people (maybe you are one of them, however) who consider this to be a detriment. For a more obvious comparison: yes, I could sit around constructing object pools in my Java program to avoid heap allocations and their associated garbage collection penalty, but if rudimentary escape analysis manages to pick up a lot of the value, even if "fragile" or nondeterministic, I might not need to spend the time anymore to worry about that kind of detail in my software as the cost benefit analysis shows that the toolchain is now a more efficient usage of resources towards that issue (I could be spending my energy working on algorithm design, for example, instead of object pools). As long as the semantics of the language support the optimization, and as it doesn't seem to be difficult to implement, it would be useful to have the compiler perform this optimization for the developer, and one would expect such an optimization to either already be there or be "in the works".
This is a very different kind of optimization than the other things you are talking about. This is something that is operating at a high level and potentially has implications that might not be obvious. You are changing the type of something and effecting the function overloads which are resolved. I think its important for what functions you are calling to be deterministic. My previous post was just touching on what possible value can be gained from allowing this and my conclusion is that it would be very little.
Also, your example of escape analysis is precisely because Java doesn't allow you to express what you are wanting to do, whereas C++ does allow you to express moves.
edit: I would compare this to C++'s copy elision, but that has a very high value and is much less intrusive.
I look at Java code and it horrifies me. Java is great if your resource is memory. It turns into a horrible unsafe mess the moment your resources are not memory.
You can write bad code in any language. No experienced Java coder would write a code like this. Java has ARM blocks for dealing with deterministic resource cleanup.
using/ARM/etc. make classes which own non-memory resources a lot less awkward to deal with, but one of the big wins of RAII is that the caller doesn't have to know or care that you have things which require special cleanup.
Agreed, however it is very easy to catch a forgotten close() automatically (IDEs do that), and, at least in my experience, most non-memory resources are used locally and not shared, so they are pretty easy.
On the other side, some programming techniques are hard or downright impossible without GC - e.g. heavy functional programming with immutable persistent structures.
Reality is there are many Java code bases which handle resources like this. For java 6 and below this not bad code; it is how you are forced to deal with resources.
Even with the new block syntax, you have painful nesting if the operation requires several disparate resources.
Blocks are an improvement, but not the same as RAII. A block is literally converted to a try-finally by the compiler. It is the same thing with a cleaner syntax. There is no safety added to the old Java way. A programmer can forget to put his stuff in a block and leak the resource the same as he forgets to use a try-finally. The onus is on the consumer, not the library writer.
With RAII the onus is removed from the consumer. The consumer writes safe code automatically, no onus to remember any special clean up idioms.
Blocks are an improvement, but not the same as RAII. A block is literally converted to a try-finally by the compiler. It is the same thing with a cleaner syntax. There is no safety added to the old Java way. A programmer can forget to put his stuff in a block and leak the resource the same as he forgets to use a try-finally. The onus is on the consumer, not the library writer.
With C++ RAII the onus is removed from the consumer. The consumer writes safe code automatically, no onus to remember any special clean up idioms.
Am I the only one who is too lazy to write concepts? The process seems too involved and represent a case of diminishing returns. Large OOP projects are infamous for having classes that are impossible to mock, this will only make the problem worse.
84 comments
[ 3.3 ms ] story [ 163 ms ] thread[1] http://channel9.msdn.com/Events/GoingNative/GoingNative-2012...
Hopefully we can have an interesting discussions on those topics now that at least 1 news about them has reached the main page.
but hey, i'm not a system programmer so i can afford it :)
And then there's tons of things that creep me out a bit. Being a multi paradigm language sounds good on paper, but it seems to cause a lot of accidental complexity. The best way to stay sane is probably to pick a certain subset of C++ for your project and stick to that, that's what I tend to do.
Then again, that's not uncommon in simpler languages either. "JavaScript, The Good Parts", lint and all that. Code accessibility matters IMO.
There are two better options:
Or, if it really has to be a pointer: Both will be automatically freed as soon as the scope is exited.This is fine until I want to do this, at which point C++ becomes a memory management bastard:
Note: I'm not trying to be nitpicky. I really don't understand what you mean when you write that you "want to do this".
Now, I don't know anything about you, but based off your comments in this thread I'm not sure you have a working knowledge of C++.
In C++, you have various ways of referencing and passing objects, so you need to be aware of the lifetime and ownership of objects. It's arguably harder, and unfortunately, the C heritage makes it a lot harder than it needs to be :(
C++ makes it possible for the library writer to take care of freeing the resources automatically.
Anyway, what if you need to share non-memory resources? Suddenly you cannot depend on the garbage collector, you cannot use try/finally, you cannot use using or try-with-resource - you need to handle the situation just like in C++, except you're given fewer tools to do it - and a poorer understanding of the situation if you've learned that you don't need to do manual resource management due to the garbage collector.
I've done a limited amount of C++ many many years ago before I even knew what garbage collection was and I keep thinking of revisiting it, but honestly in my line of work ("Enterprise") I don't need the mental overhead of dealing with things such as pointers and memory allocation.
Perhaps, my view is outdated, but I get the impression that everything in C/C++ is just a little thorny when compared to other slightly more high-level languages, such as namespaces, package management, list comprehensions, library compatibilities, type strictness etc.
I would like to be wrong about that though... I wish I had a little more motivation to spend some real time with C++ (or perhaps even C).
Of course, you may prove me wrong.
- partial construction: having to deal with already allocated pointers in case of errors during the construction - copy construction: who owns the resources? (Alternatively, you'll need to remember to disable the copy constructor explicitly.) - copy assignment: likewise
Doing the Right Thing is just so much easier when you wrap those member objects into e.g. `std::unique_ptr<T>`. That'll disable copy construction and assignment, which means that you'll need to think of that separately if it's needed.
Can you expand upon what you mean by "safe" here?
If you mean "unable to fail", I vehemently disagree. Constructors should validate what is passed into them and fail if that is invalid. The alternative is to construct a zombie object that can't actually be used. Objects like this subvert the type system and lead to lots of unnecessary "if object.is_valid()" checks all over code that uses them.
If you have a pointer in a class that is exactly what you want to happen in most cases. Then if you want copies you "have to" define your own copy constructor and assignment operator that copy the object pointed to rather than just copying the pointer.
C++'s C heritage makes it hard to master and also causes countless misconceptions. Even if you never want to use C++, it's worth looking into some of the unique concepts that, sadly, didn't catch on in other languages so far. (My personal favourites are const correctness and RAII.)
Do watch the talk, the first few minutes might already be mind blowing for anyone who thought C and C++ are basically the same language.
So if g is returned from a function or passed to a function by value, I don't know whether or not a deep copy is made. If I manipulate that Gadget, am I manipulating a shared object affecting others or is this my private copy?
Qt uses that pattern throughout, and because it is used for everything in Qt, you know that you're dealing with handles. But the C++ standard library doesn't do it that way. Almost none of the classes in the standard library are handles.
If I see a pointer, shared or otherwise, I know I'm not dealing with a deep copy. I know someone somewhere else might point to the same object.
I'd say you do not _need_ to know. If Gadget contains tons of data, it's wiser for it to just put that on the heap, internally. Like std::vector does. As long as Gadget handles (or forbids) copying, assigment etc. (rule of three), that's fine.
If you do have a huge performance issue because you're using a class that, unwisely, puts tons of data on the stack, you can use a unique_ptr instead. Kind of an edge case in my experience though.
But agreed, it's opaque when working with third party code. Gotta figure out the conventions.
Exactly. In the STL, if it looks like a value copy it is a value copy, but the handle pattern that Stroustrup prefers gives you no clue whatsoever as to what's going to happen. You have to figure it out one class at a time.
I can't count the number of bugs I've fixed due to programmers (myself, sadly, included) using IDE code completion features as a substitute for reading documentation.
For example, in the code you posted all the pointer-like handles are named 'p'. In my experience, if we consistently pursue minimal scope and minimal lifetime for our objects, then longer-lived objects are oddballs and we use the type of the handle to help describe its ownership strategy.
This is a very big problem in enterprise size code basis, specially with the rotation of external developers.
Well, at this point it's more like C++'s C++ heritage.
> const correctness
It certainly exists in plenty of other languages, but in a non-broken way. C++'s const correctness model is mostly broken, by the way [1]. D's model is a bit more sane [2]. Languages like Haskell or Clojure (AFAIK) make immutability a default. This kind of functional style is a good thing for C++ developers overall, as emphasized by John Carmack [3].
[1] Once again, the C++ FQA has plenty of wisdom to share: http://yosefk.com/c++fqa/const.html [2] http://en.wikipedia.org/wiki/Const-correctness#const_and_imm... [3] http://www.altdevblogaday.com/2012/04/26/functional-programm...
> RAII didn't catch in other languages
That's because most other languages are garbage collected. (Not that RAII is a bad thing, it's just not needed.)
What in particular is broken about const correctness in C++? That things are not const by default? I agree, they should be, but I wouldn't really call that broken. I put const wherever I can, just like Carmack. So I sort of pretend it's the default :)
I don't think it makes sense to compare const correctness in C++ to immutability in Clojure. In Clojure, you don't have objects that encapsulate internal state. const in C++ allows you to say which methods do change internal state and which don't. If you pass a const reference, the receiver can only call const methods. I just learned from you that D has this, wasn't aware of any other OOP language that has it. Not even Scala :(
> That's because most other languages are garbage collected. (Not that RAII is a bad thing, it's just not needed.)
RAII is about more than just memory, it's about any kind of resource. That includes stuff like file handles, Java's try {} finally {} is IMO a weird workaround. In C++, stream's can just close themselves when they go out of scope.
The lack of transitivity for one. Const pointers shouldn't be able to modify the pointed objects (that's the default in the D language). The fact that working with non-const-correct libraries is a nightmare (prepare to const_cast a lot!). There are plenty of other things that make this a real PITA to work with on sizable projects with legacy or outside code.
> RAII is about more than just memory, it's about any kind of resource.
That's true, yeah, though most of the time it's about memory (with smart pointers). But you're absolutely right on that point.
As far as I know, Common Lisp users have some kind of RAII with macros, but I'm not familiar enough to compare it to C++. Of course, D also has support for this idiom.
Are they? I either misunderstand or disagree. The syntax gets a bit silly with pointers, because you can have const pointers and pointers to const (and const pointers to const of course):
> The fact that working with non-const-correct libraries is a nightmare (prepare to const_cast a lot!).Agreed!
> As far as I know, Common Lisp users have some kind of RAII with macros, but I'm not familiar enough to compare it to C++.
In my experience, the kind of problem RAII is useful for is far less common in Lisps (can only really speak for Clojure). One tends to not encapsulate resource management in any way, so handling it explicitly in the caller is fine. And you can use macros to do something when the scope is excited.
So in Clojure e.g., you can use with-open to do something with a file:
with-open ensures that the reader is properly closed when the scope is exited or when an exception occurs.In C++ (or any OOP language I guess), you'd want something like this:
So I suppose it depends on the philosophy. For OOP languages, RAII makes a lot of sense IMO.That is to say if you have a constructor:
That you call like: You're hamstringing the compiler into always copying that string instead of being able to use the new move semantics, because it can't mess with the guts of a const reference. Instead, do the previously unspeakable evil of passing by value and then moving, e.g. This lets the compiler know that if string has a move constructor, and is an rvalue, it can just move the guts into place instead of performing the copy, since the variable is 'sunk' into the new location. Huge wins all around.move calls strike me as ugly and kinda dangerous. What if you do wind up wanting to refer to s in the body later on? It'd be nice if the compiler just handled it.
Then copy s, don't move it.
Do you allow, but not require, the compiler to do this substitution whenever it can prove a value is never used again? This is unreliable across build options and compilers so it would be unwise to depend on it. There is an opportunity here for a tool that could identify places where you could insert move, perhaps even a -W option for the trivial cases such as above, but I am not convinced the language should allow the compiler to do this.
Do you make it mandatory and add more special cases for the compiler to have to implement? This would require the compiler to track references to make sure they don't get passed to some other function. It would need someone to codify the special cases in the standard and this might be very difficult. It would also be fragile, there would be cases where implicit move used to kick in but some added function call inhibits it even though a move is still the right thing to do.
Also, your example of escape analysis is precisely because Java doesn't allow you to express what you are wanting to do, whereas C++ does allow you to express moves.
edit: I would compare this to C++'s copy elision, but that has a very high value and is much less intrusive.
On the other side, some programming techniques are hard or downright impossible without GC - e.g. heavy functional programming with immutable persistent structures.
Even with the new block syntax, you have painful nesting if the operation requires several disparate resources.
Blocks are an improvement, but not the same as RAII. A block is literally converted to a try-finally by the compiler. It is the same thing with a cleaner syntax. There is no safety added to the old Java way. A programmer can forget to put his stuff in a block and leak the resource the same as he forgets to use a try-finally. The onus is on the consumer, not the library writer.
With RAII the onus is removed from the consumer. The consumer writes safe code automatically, no onus to remember any special clean up idioms.
Blocks are an improvement, but not the same as RAII. A block is literally converted to a try-finally by the compiler. It is the same thing with a cleaner syntax. There is no safety added to the old Java way. A programmer can forget to put his stuff in a block and leak the resource the same as he forgets to use a try-finally. The onus is on the consumer, not the library writer.
With C++ RAII the onus is removed from the consumer. The consumer writes safe code automatically, no onus to remember any special clean up idioms.