SFML community forums

Help => General => Topic started by: Jabberwocky on February 10, 2016, 01:21:43 pm

Title: Yet another RAII discussion
Post by: Jabberwocky on February 10, 2016, 01:21:43 pm
To be honest if you stick to modern C++ with RAII memory leak tracking will become obsolete pretty quickly.

Alternatively, memory leak tracking solves the same problem as RAII does, but does not involve the extra overhead and ugly syntax of smart pointers. 

Except that memory leak tracking alone is sufficient to find any problems. 

RAII alone is not sufficient to prevent memory leaks (although it certainly helps), as it of course relies on there being no programming errors, in either your code or 3rd party library code.

So, if you're choosing between the two, memory leak tracking is a better choice.

Just another opinion.  :P
Title: Re: Yet another RAII discussion
Post by: Satus on February 10, 2016, 02:08:55 pm
Quote
Alternatively, memory leak tracking solves the same problem as RAII does, but does not involve the extra overhead and ugly syntax of smart pointers. 

It does not solve problems, most of the time it creates tons of problems on it's own.
Also what overhead are you talking about?
Title: Re: Yet another RAII discussion
Post by: Jabberwocky on February 10, 2016, 04:16:42 pm
what overhead are you talking about?

There's about a bazillion articles on it, but here's one.

http://stackoverflow.com/questions/22295665/how-much-is-the-overhead-of-smart-pointers-compared-to-normal-pointers-in-c (http://stackoverflow.com/questions/22295665/how-much-is-the-overhead-of-smart-pointers-compared-to-normal-pointers-in-c)
Title: Re: Yet another RAII discussion
Post by: Satus on February 10, 2016, 07:27:42 pm
So unique_ptr has zero or almost zero overhead.
Title: Re: Yet another RAII discussion
Post by: Nexus on February 10, 2016, 07:28:37 pm
Oh nice, another discussion about RAII. Fortunately we haven't had enough of them in the past. If there is really a need to discuss this yet another time, start another thread, and don't hijack the thread about leaks in SFML.

Alternatively, memory leak tracking solves the same problem as RAII does, but does not involve the extra overhead and ugly syntax of smart pointers.

Except that memory leak tracking alone is sufficient to find any problems.
So, when you have the choice between a technique to fix a problem after it occured, and one that prevents it from even appearing in the first place, you decide for the former. Sounds reasonable.

The arguments are always the same, and they're easily refuted. Please read this article about RAII (http://www.bromeon.ch/articles/raii.html).

http://stackoverflow.com/questions/22295665/how-much-is-the-overhead-of-smart-pointers-compared-to-normal-pointers-in-c (http://stackoverflow.com/questions/22295665/how-much-is-the-overhead-of-smart-pointers-compared-to-normal-pointers-in-c)
Maybe you should read the links you're posting. Smart pointers have no overhead, when used in a way semantically equivalent to manual memory management.

That's why one doesn't argue with shared pointers. You would have to compare it to an equivalent hand-crafted situation, i.e. shared ownership with reference counting, type-erased deleters and thread safety. Pretty sure that's not free either ;)
Title: Re: Yet another RAII discussion
Post by: DarkRoku12 on February 10, 2016, 08:06:27 pm
what overhead are you talking about?

There's about a bazillion articles on it, but here's one.

Normally you must not even notice the overhead of smart pointers.

On Channel9 from Microsoft , we can find talks about modern C++. And Bjarne Stroustrup (Creator of C++) and Stephan T. Lavavej (maintainer of Visual Studio's C++ Standard Library implementation) always recommend the use of smart pointers when possible. Without to mention that ISO C++ recommend it  (https://isocpp.org/wiki/faq/freestore-mgmt#new-never-returns-null)
Title: Re: Yet another RAII discussion
Post by: Jabberwocky on February 11, 2016, 01:34:55 pm
Oh nice, another discussion about RAII. Fortunately we haven't had enough of them in the past. If there is really a need to discuss this yet another time, start another thread, and don't hijack the thread about leaks in SFML.

Incidentally, I didn't bring it up.  I only responded because, unprompted, eXpl0it3r critiqued the use of memory leak detectors by the thread author by suggesting RAII made them obsolete (which is fine, he's sharing an opinion).  So even though you quoted me, I'll assume your sarcastic reprimand was aimed at eXpl0it3r.

Although I am still a little confused.  You ask that we not hijack this thread to discuss RAII.  You follow that statement immediately with a discussion of RAII.  That in itself does not make a lot of sense. 

Normally, the point of doing such a thing would be to have a debate.  I guess I'll assume you're not interested in such a debate however, or that your rules of hijacking threads apply only to others and not yourself.

I'm ok with that, though.  Given that you filled your response with both sarcasm and insults, things a person doesn't do when seeking a positive and productive exchange of ideas, I wouldn't have been interested anyway.  I try to communicate in a positive manner and with an open mind, even and especially with people I disagree with.  If you approach things being condescending and closed minded, you neither end up learning anything new yourself, nor convincing others of your opinion.  So the "debate" is a waste of everyone's time.
Title: Re: Yet another RAII discussion
Post by: Nexus on February 11, 2016, 05:23:31 pm
Incidentally, I didn't bring it up.  I only responded because, unprompted, eXpl0it3r critiqued the use of memory leak detectors by the thread author by suggesting RAII made them obsolete (which is fine, he's sharing an opinion).
He's actually making a good point, and there's a ton of resources to be found on the Internet that back up his claim. In this forum alone this has been discussed so often that it almost seemed like people would agree.

You ask that we not hijack this thread to discuss RAII.  You follow that statement immediately with a discussion of RAII.  That in itself does not make a lot of sense.
It was mainly a link to my article, hoping that people will read it and I don't have to re-iterate everything again. An in-depth reasoning can be found there.

Given that you filled your response with both sarcasm and insults, things a person doesn't do when seeking a positive and productive exchange of ideas, I wouldn't have been interested anyway.
Sarcasm yes, which shouldn't be surprising given that this very topic has been discussed to death already, but I don't see any insults. I'm sorry if my post came across that way. I sometimes write in a challenging way with rhetoric questions to make people reflect and question what they've heard.
Title: Re: Yet another RAII discussion
Post by: Jabberwocky on February 11, 2016, 07:56:25 pm
He's actually making a good point, and there's a ton of resources to be found on the Internet that back up his claim. In this forum alone this has been discussed so often that it almost seemed like people would agree.

So the rule is, you can discuss RAII if you are proposing it is a useful solution.

You cannot discuss RAII if you are critiquing its use as a solution - that's hijacking.
Is this correct?

Truthfully, that strikes me as a bias to shut down arguments you disagree with.

It was mainly a link to my article, hoping that people will read it and I don't have to re-iterate everything again. An in-depth reasoning can be found there.

I have read it.  It's a good article.  But it does not mean that there is not also good in-depth reasoning NOT to use RAII.  I think you are mistaken if you believe your article is programming gospel that should override any and all discussion on it.  The internet is equally filled with people who critique RAII and smart pointers as those who advocate for it.

I don't see any insults. I'm sorry if my post came across that way.

Thanks for saying sorry, I appreciate it.
There's a few things which were pretty inflammatory.  Here's one:

Maybe you should read the links you're posting. Smart pointers have no overhead, when used in a way semantically equivalent to manual memory management.

I obviously did read the article.  I wouldn't have posted it otherwise.  That's an extremely dismissive and insulting thing to say.  I would also argue you're incorrect.

Here's a quote from the top rated answer from that article.

Quote
std::unique_ptr has memory overhead only if you provide it with some non-trivial deleter.

std::shared_ptr always has memory overhead for reference counter, though it is very small.

std::unique_ptr has time overhead only during constructor (if it has to copy the provided deleter) and during destructor (to destroy the owned object).

std::shared_ptr has time overhead in constructor (to create the reference counter), in destructor (to decrement the reference counter and possibly destroy the object) and in assignment operator (to increment the reference counter).

Granted, he does say the performance hit is small.  But it is 100% incorrect to say they have no overhead.  So not only did I read the article, but it explicitly talks about the overhead of smart pointers.  Relevant to the discussion, wouldn't you say?

Now, your argument is that
Quote
Smart pointers have no overhead, when used in a way semantically equivalent to manual memory management.

But that is not the discussion we're having.  No one has suggested we're merely replacing regular pointers with unique_ptr.  The discussion is whether to use RAII vs. not using RAII.  RAII, in cases where you need multiple pointers to the same memory, advocates the use of shared_ptr (even if it does recommend using it sparingly).  So it is not a choice merely between raw and unique_ptr.  That's a false equivalency.

There are certainly places in my (non-RAII) code where I have multiple pointers to the same memory.  This is wise not to overuse, but it has its place.  I do not use shared_ptr.  I do not use reference counting, either.  Rather, I have a well structured and known lifetime of this "multiply pointed to" memory, which can safely guarantee the duplicate pointers will be safe to access where used in the code.

This is faster, with less overhead, than the corresponding RAII solution would be.  Personally, in situations like this, I also find being closer to the metal, using raw pointers rather than the added abstraction of smart pointers, allows me an easier mental model of what is actually happening. I consider this valuable.

Here's another quote from the 2nd highest rated answer:

Quote
You can expect some overhead in debug builds, since e.g. operator-> must be executed as a function call so that you can step into it (this is in turn due to general lack of support for marking classes and functions as non-debug).

In my particular case, debug performance is very important.  This is because my primary code cycle is:  code, compile debug, run debug.  This is mainly because my projects are big enough that release linking is slow, which has a significant impact on the code-compile-run turn around cycle.  And there is the added advantage of being able to step immediately into the code without a mangled stack.

________

Now, I'm not saying RAII is terrible.  I think there are scenarios where it is a useful tool.  Perhaps one is in large code bases maintained by multiple programmers of various skill level, where the extra layer of safety is necessary, or the programmers lack the skill, experience, or even the just perfected habits to manage their memory manually.

For me, RAII is like someone suggesting I wear a helmet to bed, in case a meteor crashes through my roof and kills me.  I'm just not worried about the problems that RAII helps solve.  I am happy with my existing solutions.  They work fine.

You argue against this in your article.  You call this ignorant.  Let me ask you this - SFML does not use smart pointers.  Is this ignorant?  How often is SFML affected by it's lack of smart pointers?  How about in a way that has not been trivially easy to fix?  I certainly have never had a problem with SFML's use of raw pointers.

And finally,
So, when you have the choice between a technique to fix a problem after it occured, and one that prevents it from even appearing in the first place, you decide for the former. Sounds reasonable.

That's both sarcastic and snippy by the way.  ;)
But it doesn't attempt to address the point I made.  Which is that RAII does not guarantee you've fixed the problem.  As I said originally, yes, it helps prevent it.  But it does not guarantee there are no programmer errors, either in your code base, or in those of the libraries you employ.  The only guarantee is a memory leak detector.

If you'd like to call into question my reasonableness, please do so by addressing the reasoning I presented.

A final word - I get that you like RAII.  I get that it works for you.  That's cool.  But, as they say, programming is as much an art as a science.  To claim that people are foolish who chose to employ their art differently than you, that their reasoning is debunked, is a narrow minded view of the art of writing code. 

You may disagree with my reasoning.  And I with yours.  That's fine.  But I do not disrespect you for your decision.
Title: Re: Yet another RAII discussion
Post by: Nexus on February 11, 2016, 09:40:24 pm
Ok, seems like we've definitely reached a point where we can split the topic. I originally didn't want to write all this, but you seem to be interested in the topic, and hopefully we can have a reasonable discussion.

So the rule is, you can discuss RAII if you are proposing it is a useful solution.
No. eXpl0it3r was not discussing but mentioning RAII. The discussions have occured 50 times here already, and counter-arguments have been defeated so often, that I felt there'd be no point in bringing the same ones up yet another time, while ignoring the counter-arguments to them. You may not have witnessed that, though.

I obviously did read the article.  I wouldn't have posted it otherwise.  That's an extremely dismissive and insulting thing to say.
It seemed to me that you were picking only the bits you like from that SO thread, in order to support your argument: "memory leak tracking [...] does not involve the extra overhead and ugly syntax of smart pointers" implying "smart pointers have extra overhead", which is wrong. Without further relativation this statement is misleading.

I would also argue you're incorrect.
Quote
std::unique_ptr has memory overhead only if you provide it with some non-trivial deleter.
That's exactly why I mentioned the "equivalent" part and even put it in italics. You're comparing apples with oranges. In 99% of the time one does not use custom deleters. And in the remaining 1% where one does, one would have to do the same thing with new/delete.

Granted, he does say the performance hit is small.  But it is 100% incorrect to say they have no overhead.  So not only did I read the article, but it explicitly talks about the overhead of smart pointers.
Sorry, but that's not true. You can very well achieve zero performance overhead with smart pointers, which was one of the core design principles of std::unique_ptr. And the SO thread mentions this if you look carefully: "std::unique_ptr has memory overhead only if you provide it with some non-trivial deleter." => "it has no overhead if you don't".

Rather, I have a well structured and known lifetime of this "multiply pointed to" memory, which can safely guarantee the duplicate pointers will be safe to access where used in the code.
Yes, and typically, you can design your code in a way so that one of these pointers owns the memory and the others are just referrers. If you know in advance who frees the memory, you don't need shared ownership. I'm not just saying this, I've written quite a lot of C++ code and really rarely felt the need for std::shared_ptr. Most of it is choosing a proper design, with clear ownership semantics. I agree that it may be more difficult in C++ than in other languages, but std::shared_ptr is truly overused.

Personally, in situations like this, I also find being closer to the metal, using raw pointers rather than the added abstraction of smart pointers, allows me an easier mental model of what is actually happening. I consider this valuable.
Fallacy #1: "I am using new/delete because I need more control over my code."
According to this logic, you'd program in C, if not Assembly.

In my particular case, debug performance is very important.  This is because my primary code cycle is:  code, compile debug, run debug.  This is mainly because my projects are big enough that release linking is slow, which has a significant impact on the code-compile-run turn around cycle.  And there is the added advantage of being able to step immediately into the code without a mangled stack.
I see, but I would really be surprised if this a) could not be deactivated (VAX has a step filter feature, so it's a matter of tools), and b) this would have a measurable impact, and c) this impact would outperform the time you lose if you make only one mistake related to manual memory management. You're paying a very high price for this.

Now, I'm not saying RAII is terrible.  I think there are scenarios where it is a useful tool.

For me, RAII is like someone suggesting I wear a helmet to bed, in case a meteor crashes through my roof and kills me.
Fallacy #5: "Unique pointers are overkill in this situation"

I'm just not worried about the problems that RAII helps solve.  I am happy with my existing solutions.  They work fine.
Fallacy #3: "Memory management is only unsafe when you use it incorrectly/in an unsafe way"

Let me ask you this - SFML does not use smart pointers.  Is this ignorant?
No, it's a consequence of smart pointers not being available in C++98, and difficult to hand-craft in a useful yet not dangerous way (to enable resource transfer). But I can assure you that we are going to use them once we switch to C++11, as well as that there have already been countless unnecessary bugs because resources were not deallocated correctly. Just look at Font.cpp, it's a big mess with tons of different resources that all need to be deallocated in different ways, and failure to load one must account for all the others. A C++11 handle class with move semantics would simplify this code dramatically, while making it more robust at the same time.

I certainly have never had a problem with SFML's use of raw pointers.
You have not been developing it. Because in the API, SFML doesn't use raw pointers that own memory. At least I don't recall an interface that expects you to allocate something with new, or gives you something that you must deallocate with delete.

That's both sarcastic and snippy by the way.  ;)
No, I'm dead serious. I don't understand why one would prefer spending time to fix errors to not making them in the first place.

Which is that RAII does not guarantee you've fixed the problem.
Fallacy #4: "Many people use smart pointers incorrectly"

Yes, it does (depending on what the problem is). When you prohibit access to manual resource management and enforce the use of strict ownership RAII classes, it's not possible to leak resources. How would it be? The destructor is guaranteed to be deterministically called -- abrupt program terminations aside. There may be a few situations where you need a more flexible/loose form of RAII, like shared ownership. And there are higher-level "leaks" like cyclic references or std::vector::reserve() that are out of RAII's scope. But it's an extremely powerful tool. You can still use a memory leak detector in addition to RAII if you need the extra safety; the fact that it can find mistakes with raw memory management is not an argument against techniques that prevent them by design.

Programmer errors are not a good argument because bugs in the memory leak detector can occur as well, and even more so with manual memory management. And as many threads in this forum (maybe this one too?) show, it's possible to use leak detectors incorrectly, or interpret their results in a wrong way. So it only guarantees something as long as you use it correctly, and we're back to the circular logic.

A final word - I get that you like RAII.  I get that it works for you.  That's cool.  But, as they say, programming is as much an art as a science.  To claim that people are foolish who chose to employ their art differently than you, that their reasoning is debunked, is a narrow minded view of the art of writing code.
I provided a ton of rational arguments for RAII, I even skimmed through many places in the Internet to collect different views on the topic and write a detailed article about it. It's not a matter of taste or "art" (fallacy #6), but hard facts. And I did not call you foolish.
Title: Re: Yet another RAII discussion
Post by: Jabberwocky on February 12, 2016, 07:05:05 am
It seemed to me that you were picking only the bits you like from that SO thread, in order to support your argument:

You mean like you just did when you only quoted the single line on unique_ptr?  And chose to delete the parts specifically discussing the overhead of other types of smart pointers?

implying "smart pointers have extra overhead", which is wrong. Without further relativation this statement is misleading.

It is not wrong.  Quit saying that.  Again, you are the one who is "picking only the bits you like" from the SO article.

The article lists the overhead associated with various different smart pointers.
That list does not say "smart pointers have no overhead".
That list does say "these types of smart pointers have this kind of overhead, which in some cases is zero.

It is fine for you to point out unique_ptr has no overhead. 

It is not fine for you to keep conveniently omitting the costs associated with shared_ptr.

It is not fine for you to keep calling me wrong, when the article clearly and plainly states there is overhead with certain aspects of smart pointers.

If you want to discuss the responsible and limited use of shared_ptr, due to the overhead it incurs, cool.  That is an informative conversation, based on the facts.

Unfortunately I'm going to need to ignore the next couple pieces of your post that continually reiterates the argument that smart pointers have no overhead.  Again, arguing for the restricted use of unique_ptr only is a different thing.  We can do that.  But it is different.

Yes, and typically, you can design your code in a way so that one of these pointers owns the memory and the others are just referrers. If you know in advance who frees the memory, you don't need shared ownership.

Ok, now we're reaching somewhere useful.
What do you do in this scenario?
Do you use a unique_ptr as the "memory owner" pointer, and a raw pointer in the "non-memory owning" copies?

If so, I actually think that is a reasonable solution.  But I think a lot of RAII advocates would have a problem with this.

But I'd also like to point out that manual memory management is completely sufficient in my case.  For my manager class which owns the memory, a simple delete call in the destructor completely handles the problem.

Fallacy #1: "I am using new/delete because I need more control over my code."
According to this logic, you'd program in C, if not Assembly.

If someone wrote a program in C, it worked, and it didn't leak memory, I would be 100% ok with this.  If they further claimed C provided all the tools they needed to achieve it, I would be 100% ok with this.

You keep speaking in absolutes.  I think this is where we will ultimately reach a stand still in our debate.  I believe in very few absolutes when it comes to coding.  I take into consideration the strengths of the coder, the requirements of the task, and the characteristics of any existing code base.  I am wiling to accept different tailored solutions (e.g. RAII vs. no RAII) depending on these factors.

regarding link-time:

I see, but I would really be surprised if this a) could not be deactivated (VAX has a step filter feature, so it's a matter of tools), and b) this would have a measurable impact,

I appreciate your suggestions.  But with large programs in visual studio, there's only so much you can do.  It's not just in my own code bases I've encountered slow link times, but also at every job I've ever worked at working on large software.

and c) this impact would outperform the time you lose if you make only one mistake related to manual memory management. You're paying a very high price for this.

You have no idea how much time, or how many mistakes I make using manual memory management.  You can assume, if you like.  Perhaps based on your own bad experiences.  But you should not project these issues onto me.

Fallacy #5: "Unique pointers are overkill in this situation"

Fallacy #XI:  "A single programming technique must be applied to all problems"

Fallacy #3: "Memory management is only unsafe when you use it incorrectly/in an unsafe way"

Fallacy #VII:  Manual memory management cannot be done safely.  It can, and has across many years in millions of programs.

Just look at Font.cpp, it's a big mess with tons of different resources that all need to be deallocated in different ways, and failure to load one must account for all the others. A C++11 handle class with move semantics would simplify this code dramatically, while making it more robust at the same time.

I had a look at Font.cpp.  Based on a very quick brows of the code:
There are 3 variables that use new/delete:  m_refCount, m_stream, and a m_streamRec. 

There are 5 calls to new.
There are 6 calls to delete.
That hardly strikes me as an unmanageable "big mess".

Some of these are due to a hand-rolled pointer-based reference count.  I probably would have advocated for a different solution.  I would not have objected to smart_ptr in this situation. 

There appears to be other dynamic memory allocation via calls to FT_Get_Glyph and FT_Done_Glyph.  Based on my quick examination, this does not appear to be something smart pointers would help with, as you are restricted to the syntax provided by FreeType.

If you consider this an unmanageable problem without smart pointers, I would suggest we have different abilities to deal with manual memory management.

You have not been developing it. Because in the API, SFML doesn't use raw pointers that own memory. At least I don't recall an interface that expects you to allocate something with new, or gives you something that you must deallocate with delete.

Yes, SFML is written with good memory management.
I have found the code a pleasure to work with.

Fallacy #4: "Many people use smart pointers incorrectly"

Fallacy#XXIII:  Smart pointers are always used correctly.
Fallacy#XXIV:  Smart pointers will guarantee your programs (and libraries you employ) do not leak memory.

There may be a few situations where you need a more flexible/loose form of RAII, like shared ownership. And there are higher-level "leaks" like cyclic references or std::vector::reserve() that are out of RAII's scope.

Exactly.
So we are in agreement memory leaks may occur regardless of the use of RAII.
That was the primary point of my original post.

Programmer errors are not a good argument because bugs in the memory leak detector can occur as well, and even more so with manual memory management. And as many threads in this forum (maybe this one too?) show, it's possible to use leak detectors incorrectly, or interpret their results in a wrong way. So it only guarantees something as long as you use it correctly, and we're back to the circular logic.

Yes, memory leak detectors can be used incorrectly.
Yes, RAII can be used incorrectly.
Anything you use incorrectly can cause problems.

I provided a ton of rational arguments for RAII, I even skimmed through many places in the Internet to collect different views on the topic and write a detailed article about it. It's not a matter of taste or "art" (fallacy #6), but hard facts. And I did not call you foolish.

Pardon, you say ignorant, not foolish.

As noted above, I believe we are at an impasse.  We believe fundamentally different things about the art of coding.

I did want to respond though, to clarify my opinion.  And to refute your repeated assertions that I was incorrect.  If you want to keep posting that, there is nothing more I can do than what I have written above. 

I also responded to to demonstrate how unhelpful it is to post these absolute "fallacy" rules, when I can just as easily post opposing fallacies.  Discussing in terms of these false absolutes does nothing to further the conversation.

However, I am genuinely interested in your technique in dealing with "multiply pointed to" memory in RAII, without the use of shared pointers.  Should you decide to elaborate.  I appreciate that aspect of the discussion.
Title: Re: Yet another RAII discussion
Post by: eXpl0it3r on February 12, 2016, 09:26:04 am
How was that again with the sarcastic tone you didn't like? ::)

Anyways there's quite a bit of nitpicking going on while if one looks at the discussed points more broadly it would be clear what was meant even if it wasn't pedantically written that way.

Do smart pointers have an overhead? Yes, some of them do, mainly that ones that one should only use in rare situations and when used in such situations similar overhead would have to be introduced when manually managing memory (shared ownership/ref counting).

Does manual memory management introduce a higher probability of making a mistake (e.g. creating a memory leak)? Yes, due to the various ways a scope can be prematurely exited, the deletion of allocated memory requires more care and thus a higher probability in forgetting one or the other scenario.

Is it impossible to write non leaking applications with manual memory management? No.

Is it impossible to write bug free applications? Yes.

Should manual memory management be suggested as best practice to a new beginner or even "advanced" C++ programmer? No, because the gained performance (if any!) through MMM will not outweigh the high probability of making mistakes, in almost any application said programmer will write.

Are leak detection software useless? No, but through the guarantees that RAII makes, the chance of "random" memory leaks goes towards zero.

And finally a small analogy:
Just because a Formula 1 driver is able to drive at 200-300 km/h on normal roads, doesn't mean one should remove all the tempo limits and let everyone drive at whatever speed they want.

Since you claimed that there are many people advocating against RAII, I'd be interested in reading some sources (as long as they aren't just C advocates, we're talking here C++ after all).
Title: Re: Yet another RAII discussion
Post by: Nexus on February 12, 2016, 09:51:31 am
I'll only address the parts that are objectively about RAII. I don't think the meta-discussion about who said what and who is right on what topic leads anywhere.

Quote
Do you use a unique_ptr as the "memory owner" pointer, and a raw pointer in the "non-memory owning" copies?

If so, I actually think that is a reasonable solution.  But I think a lot of RAII advocates would have a problem with this.
Yes exactly. I don't see a problem with raw pointers, only with those that own memory.

And this is not directly related to RAII (or even pointers). It happens with all kinds of indirections in C++: pointers, references, iterators, handles... Those may become invalid. But my personal experience is that programs are generally much easier to understand and to reason about, when ownership semantics are made clear (i.e. this class holds this resource, and these classes may access it at these times). Shared pointers as a poor man's garbage collector are hardly ever a good idea (and unfortunately, many people treat them as such).


Quote
I had a look at Font.cpp.  Based on a very quick brows of the code:
There are 3 variables that use new/delete:  m_refCount, m_stream, and a m_streamRec. 

There are 5 calls to new.
There are 6 calls to delete.
That hardly strikes me as an unmanageable "big mess".
This is only memory used with C++ allocation operators. There are many other resources (e.g. FT_Library, FT_Face, FT_Stroker) which are manually managed, too.

You are right that smart pointers would not directly help (although something could be bent with custom deleters). But a customized RAII handle class could. I have a class template in mind that knows the resource type, and functions to allocate/deallocate instances. It would make sure that the deallocate function would be invoked in its destructor. Furthermore, move constructors would allow for resource transfer (e.g. between Font instances, or from local to member scope).

The idea is that this pattern:
a = load();
if (!a)
    return false;

b = load(a);
if (!b)
{
    unload(a);
    return false;
}

c = load(a, b);
if (!c)
{
    unload(a);
    unload(b);
    return false;
}

m_a = a; // transfer to member variables
m_b = b;
m_c = c;
 
could be simplified with RAII:
a = load();
if (!a)
    return false;

b = load(a);
if (!b)
    return false;

c = load(a, b);
if (!c)
    return false;

m_a = std::move(a);
m_b = std::move(b);
m_c = std::move(c);

Or even better, with exceptions. This would allow to decouple error handling, and make sure any so-far allocated resources are deallocated correctly.
a = load();     // may throw
b = load(a);    // may throw
c = load(a, b); // may throw

m_a = std::move(a);
m_b = std::move(b);
m_c = std::move(c);


Quote
Fallacy #VII:  Manual memory management cannot be done safely.  It can, and has across many years in millions of programs.
That's a strawman, the fallacy "Memory management is only unsafe when you use it incorrectly/in an unsafe way" doesn't imply yours. It's not a matter of whether it's possible to do MMM (manual memory management) correctly, but of how easy it is to do. Things that make my life easier as a programmer are good. And I claim applying RAII correctly is much easier than applying MMM correctly.


Quote
Fallacy#XXIII:  Smart pointers are always used correctly.
Fallacy#XXIV:  Smart pointers will guarantee your programs (and libraries you employ) do not leak memory.
Again, strawman. I'm not claiming either of those.

Your argument is: because mistakes are possible with both MMM and RAII, it doesnt matter what to choose.
My argument is: RAII helps considerably reduce, if not prevent, those mistakes. It should thus be chosen over MMM where possible.
Title: Re: Yet another RAII discussion
Post by: Jabberwocky on February 12, 2016, 10:35:39 am
How was that again with the sarcastic tone you didn't like? ::)

Actually, I don't believe there was a single instance of sarcasm anywhere in my post. 

If perhaps you are noting an element of frustration, I would agree.  I did resort to a "fight fire with fire" on the "Fallacy" thing, but that was to illustrate a point.

Anyways there's quite a bit of nitpicking going on while if one looks at the discussed points more broadly it would be clear what was meant even if it wasn't pedantically written that way.

I agree with this also.

Really, Nexus and I got caught up in who is right and who is wrong, when really we likely would have both quickly agreed on the following:

Some smart pointers have overhead.  Others do not.

The overhead is small for shared_ptr, but it exists.

The overhead of using smart pointers can be minimized, or eliminated by reducing or avoiding the use of shared_ptr.

We may have debated the merits of shared_ptr and it's use in RAII, but unfortunately we didn't get that far.

I would say to Nexus, your way of approaching things right from the start put us at odds.  I am partially to blame for getting dragged in, even though I tried not to.  Still, I would suggest you may reconsider the way you approach a debate here on the forums.  You're obviously a very smart guy.  I very much appreciate the work you and the SFML team do.  I think it is brilliant software.  But nobody likes being berated. 

Perhaps you don't think you did that.  This is very much how it came across to me.  You can decide whether you want to believe that to be valid or not.  I'm just letting you know.

As fans of programming, graphics, games, and open source software, all of us here have so much in common.  It's a shame we cant treat one another with more respect.  Yet somehow, small differences in the way we approach our craft leads to this kind of showdown.  I wish we could respect those differences, even if we do debate them.  I have tried to reinforce this message throughout my posts.  But readily admit I got pissed off eventually.

Does manual memory management introduce a higher probability of making a mistake (e.g. creating a memory leak)? Yes, due to the various ways a scope can be prematurely exited, the deletion of allocated memory requires more care and thus a higher probability in forgetting one or the other scenario.

Interestingly enough, I almost never have to "new" anything outside of a constructor.  In these cases, there are no concerns with scope. 

I do use tons of stl containers.  I normally prefer containers of data over containers of pointers to data, as interation time and cache use is far better.  So there is no concern here either for new and delete, and scope issues.

Should manual memory management be suggested as best practice to a new beginner or even "advanced" C++ programmer? No, because the gained performance (if any!) through MMM will not outweigh the high probability of making mistakes, in almost any application said programmer will write.

Well, I think many would use the same logic to recommend C# over C++.  Yes, I know garbage collection is far more sinister than anything I dislike about smart pointers.  But if the argument is to be absolved of memory management, C# is superior.  Many people choose C++ over C# precisely because they wish to manually control their memory. 

I understand you disagree with this.  I am just stating what I believe.

Since you claimed that there are many people advocating against RAII, I'd be interested in reading some sources (as long as they aren't just C advocates, we're talking here C++ after all).

Sure.
Here's one.
http://seanmiddleditch.com/dangers-of-stdshared_ptr/

Now, that article is primarily against shared_ptr, although RAII is mentioned.

Quote
RAII is not a panacea. shared_ptr is an application of RAII for example. It's simply important to note that RAII - if used correctly - allows problems to be solved that automatic GC does not.

I think that article is a balanced and insightful debate of the sort we were having trouble achieving here.

Nexus, while I was writing this up, you posted your response.  I agree with you on the meta-discussion.  I suggest we drop the fight over whose fallacies are, uh, more fallacioius.  ;) 

Regarding the example you posted - yes, I would agree this is a tricky (albeit manageable) example of memory management. 

Honestly, in the case of your example, if a person said they wanted to use a smart pointer to avoid the problems of scope and early returns, I would be fine with that.

I do not think that argument extends to insisting smart pointers be used in every single scenario where memory is allocated, a core principle of RAII.  If the allocated memory can be completely handled by the tried and true method of a proper constructor, destructor, and copy/move/assignment/etc operators as necessary, I do not see this as a danger except in the hands of an inept programmer.

This above pattern is by far the most common use of managed memory I encounter in my code.
Title: Re: Yet another RAII discussion
Post by: Jabberwocky on February 12, 2016, 10:58:33 am
I just had a look at my use of new/delete.  I should add that I often use "new" in ResourceManager type classes, and this occurs outside of the constructor. 

I have never once had a problem with memory leaking with these classes. 

Title: Re: Yet another RAII discussion
Post by: Nexus on February 12, 2016, 11:29:38 am
Good that we're back on a meaningful discussion. Thanks for the advice, I see that some parts of my post didn't come across nicely. As mentioned, this topic has come up so often here, that I was annoyed that the same arguments were brought again, ignoring all my (and other's) previous efforts to explain the matter. As I also said, I'm aware that you may not have been part of those discussions though.

So let's get back to topic:

Quote
Now, that article is primarily against shared_ptr, although RAII is mentioned.
Indeed, the article argues against shared_ptr, not RAII. It would be really interesting to see some founded argument against RAII. What I've seen so far are mostly points along "I don't need it", "it's a matter of taste/code style" and "I can program correctly without it". And I can't take them seriously, as they're 100% replacable and generic.

Quote
I do not think that argument extends to insisting smart pointers be used in every single scenario where memory is allocated, a core principle of RAII.  If the allocated memory can be completely handled by the tried and true method of a proper constructor, destructor, and copy/move/assignment/etc operators as necessary, I do not see this as a danger except in the hands of an inept programmer.
But already here, you have to write and maintain more code than with smart pointers. Smart pointers can relieve you from implementing the destructor, and when they're really smart, even the Big Three/Five.

This is what I always encounter: no argument against RAII, just saying that something can be achieved without it. But where's the advantage in abandoning it?
Title: Re: Yet another RAII discussion
Post by: Nexus on February 12, 2016, 11:45:33 am
Maybe a simple example. I really wonder how this can be done without smart pointers (I actually do, because I currently don't have an idea that doesn't involve loads of boilerplate code).
class GameObject
{
public:
    explicit GameObject(?);
private:
    sf::Drawable* mDrawable;
}

The user should be able to pass any implementation of a drawable object -- sf::Sprite, sf::Text, sf::Shape, etc. And the object should own the drawable, so it must not depend on the sf::Drawable instance being stored elsewhere.

1. How would you make sure the user can pass anything he likes, and the object takes care of properly deallocating the drawable?

2. How do you signal in the GameObject constructor signature that it takes ownership of the passed object, and doesn't just reference it?

3. How would you make GameObject copyable? This is really tricky, because you need a polymorphic copy (if a sprite is stored, a new sprite must be created, etc.). Obviously, the sf::Drawable class hierarchy can't be changed for a clone() method.
Title: Re: Yet another RAII discussion
Post by: Jabberwocky on February 12, 2016, 12:32:53 pm
Cool.

Indeed, the article argues against shared_ptr, not RAII. It would be really interesting to see some founded argument against RAII. What I've seen so far are mostly points along "I don't need it", "it's a matter of taste/code style" and "I can program correctly without it". And I can't take them seriously, as they're 100% replacable and generic.

One enlightening part of this discussion is that you are ok with using raw pointers for non-owning pointers, as we discussed earlier.  That would largely remove the necessity for shared_ptr, which is one of my primary technical objections to the use of RAII.  Out of curiosity, would you describe this as the dominant view of RAII advocates?  I would have thought not. 

For example, in the Wiki for RAII (https://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization), shared_ptr is explicitly mentioned as follows.

Quote
Ownership of dynamically allocated objects (memory allocated with new in C++) can also be controlled with RAII, such that the object is released when the RAII (stack-based) object is destroyed. For this purpose, the C++11 standard library defines the smart pointer classes std::unique_ptr for single-owned objects and std::shared_ptr for objects with shared ownership. Similar classes are also available through std::auto_ptr in C++98, and boost::shared_ptr in the Boost libraries.

I wanted to check out how you implement RAII in practice.  So I checked out the Thor source code.  I'm not doing this to attack you.  Just simply to see and understand your approach to RAII.

I do notice in Thor, you do seem to stick largely to smart pointers.

If I search the Thor source code for shared_ptr vs. unique_ptr, you do make use of a lot of shared_ptr.  I think I found it in at least equal measure to unique_ptr, based on a rough count of the search results.

So, if RAII does advocate the use of shared_ptr (which I think it commonly does), and your implementation of RAII uses shared_ptr (which it appears to), then the article I posted critiquing shared_ptr is also a critique of RAII, as it employs shared_ptr.

I think that leaves us here.  Either:

1. You accept shared_ptr as part of RAII, in which case we should debate the merits of the article I linked critiquing shared_ptr.

Or,
2. You do not accept shared_ptr as part of RAII.  This would appear to be counter to your code, but I accept you may have changed your stance on things.  If so, this changes the debate considerably.

Is that fair?
Title: Re: Yet another RAII discussion
Post by: Jabberwocky on February 12, 2016, 12:47:37 pm
class GameObject
{
public:
    explicit GameObject(?);
private:
    sf::Drawable* mDrawable;
}

My approach would be somthing like this:
(pseudo-code, didn't compile).

enum DrawableType
{
   Sprite,
   VertexArray,
   CircleShape,
   RectanbleShape,
   // ...
};

class GameObject
{
public:
   GameObject(DrawableType type);

private:
   sf::Drawable* mDrawable;
   DrawableType mType;
};


GameObject::GameObject(DrawableType type)
{
   mType = type;
   switch (type)
   {
      case Sprite:
         mDrawable = new sf::Sprite;
         break;

      // etc
   }
}

GameObject::GameObject( const GameObject& rhs)
{
   switch (mType)
   {
      case Sprite:
         mDrawable = new sf::Sprite;
         *mDrawable = *rhs.mDrawable;
         break;

      // etc
   }
}

// ... similar code to above for other operators like assignment ...

GameObject::~GameObject
{
   delete mDrawable;
}
 

There is no question of ownership, as the pointer only exists within GameObject, and is allocated in the constructors.

What would your implementation be?
Title: Re: Yet another RAII discussion
Post by: Jabberwocky on February 12, 2016, 01:08:18 pm
I should add -
my implementation above does not have an API to configure the sf::Drawable*.  Obviously you'll want to do that.

I would probably have some sort of GameObjectManager class, with a series of Create* functions that allow various parameters with which you can configure the Drawable*.  Different functions and parameters may be required for different Drawable subtypes.

Alternatively, if you need to configure the sf::Drawable* past GameObject construction, and you need to access specific functions of the derived class (e.g. sf::VertexArray::append), then a down-cast from sf::Drawable* is inevitable.  I believe this would be true in any other proposed implementation as well.  Although the DrawableType data member should allow you to safely do this with a static_cast rather than a dynamic_cast.
______

Now, here's how I actually do it in my code.

I primarily make use of sf::VertexArray, and sf::Sprite.  I don't use most other drawables.  I use separate GUI middleware, for example, so sf::Text is unnecessary.

I actually have 2 separate classes,
class ManagedSprite
and
class ManagedVertexArray

These classes create an sf::Sprite and sf::VertexArray in their constructor, and store this as a pointer data member.  It is copied and destroyed through all the usual constructor, operator, and destructor methods.  They also contain a data member which specifies their draw order in the game. 

These classes are owned by a manager.  I call it DrawManager.  The ManagedSprite are stored in one std::vector.  The ManagedVertexArray are stored in another.  They can be looked up by a ManagedSpriteID and ManagedVertexArrayID, which corresponds to indices in these std::vectors.  Game entities (actually the Drawable component of game entities) store ManagedSpriteIDs and ManagedVertexArrayIDs only, and never a pointer.  So, it's your typical "handle" implementation.  But the handle can be used to lookup and modify these drawables as needed.

The DrawManager maintains a separate list of ManagedSpriteID and ManagedVertexArrayID in draw order.  This is how I know which drawables need to be drawn overtop of which others.

The separate draw order list is necessary so that I never have to reorder the vectors storing ManagedSprites and ManagedVertexArrays.  As this would invalidate the IDs stored by components, which are indices into these arrays.

The point of all this is that I don't actually have any kind of GameObject which requires a polymorphic pointer to an sf::Drawable.

Some advantages to this approach are:
Title: Re: Yet another RAII discussion
Post by: Nexus on February 12, 2016, 01:28:30 pm
I think we shouldn't mix RAII and shared_ptr. RAII is really only the basic idiom (Resource Acquisition is Initialization, or more appropriately, RRID = Resource Release Is Destruction). The main idea is that lifetimes of a resource is bound to the lifetime of an object, and because objects are deterministically destroyed in C++, so are their bound resources.

shared_ptr combines this with several additional features:
The reason why shared_ptr is always mentioned together with RAII is because it was the first popular smart pointer. In C++98, boost::shared_ptr was very widely used, and many conceived it as a great general-purpose solution. I'm sad that Boost advertises it to this day to be suited for the PImpl idiom and other scenarios where std::unique_ptr would suffice.

You're right that I use it in Thor. In some cases (thor::ResourceHolder), it's to do exactly what its original purpose is: shared ownership. A user can load several resources, but doesn't know if the holder outlives them, so he can make sure that every resource is kept alive as long as it's in use. In combination with a custom deleter, it's furthermore possible to automatically release a resource when the last reference goes. In other scenarios (like thor::Connection), I need a safe way to check whether an object has already been deleted (disconnected), and shared_ptr + weak_ptr basically gives me that for free. So I did not have to implement my own tracking system to know when an object goes out of scope, but it would certainly have been possible.

Since Thor is a library and not an executable project, the code is generally a bit more generic and tailored to support different use cases (you'll find many templates, for example -- more than I typically have in my code). I'm wondering how many Thor users actually benefit from those shared pointers, though.



My take would be:
class GameObject
{
public:
    explicit GameObject(aurora::CopiedPtr<sf::Drawable> ptr)
    : mDrawable(std::move(ptr))
    {
    }

private:
    aurora::CopiedPtr<sf::Drawable> mDrawable;
};

That's all.

Usage:
// Instantiation:
auto sprite = aurora::makeCopied<sf::Sprite>();
sprite->setPosition(...);
sprite->setTexture(...);

GameObject obj( std::move(sprite) );

// Deep copy:
GameObject copy = obj;
// copy and obj now contain two independent sprites
// if you change the initialization to sf::Text, it will work too

That's really everything. aurora::CopiedPtr (http://www.bromeon.ch/libraries/aurora/tutorials/v1.0/smartptr.html) takes care of copying, assigning and destroying the internal object. And it knows whether it stores a sprite, shape or text. There's no single case differentiation, and I do not need to implement copy/move constructor, assignment operator, or destructor. The code is extensible as one need not know the classes in advance, works without a type enum, and supports any sf::Drawable (not just predefined ones). The GameObject.cpp does not need to include the headers for sf::Sprite, sf::Text, etc, and is thus only dependent on the base class sf::Drawable.

And most importantly, the code is extremely short and easy to maintain, in my opinion.

Again, this is more about aurora::CopiedPtr being smart, rather than RAII, but it shows what smart pointers can achieve. I find it amazing that this idiom is so unknown in the C++ community, and it seems to be accepted practice to implement The Big Five even though it's often unnecessary.
Title: Re: Yet another RAII discussion
Post by: Jabberwocky on February 12, 2016, 02:18:39 pm
That aurora::CopiedPtr looks pretty cool! 

The only objection I might have is "the principle of least astonishment" (https://en.wikipedia.org/wiki/Principle_of_least_astonishment)

You're obviously straying pretty far off the code people expect to see.  If you were to argue, "it's worth it, look how much less code I have to type!  That makes everything safer, too".  I'd say that is a good argument.

If someone else were to argue, "Listen man, I'd prefer to type more code than have some weird templated smart pointer black magic obscuring all my logic, and changing the established way C++ is written", I'd call that a good argument, too. 

This may again be an instance where I would be fine with either approach, whereas perhaps you might view the second argument as inadequate.  But I don't want to put words in your mouth.

Either way, nice work with the masterful smart pointer wizardry.

Onto RAII:

RAII as a principle I have no problem with. 

I'll leave that statement by itself, with italics for added impact.  ;)  We can base the rest of our discussion with that agreement in place.  All my arguments against RAII throughout have been related to the RAII implementation in C++.

If you don't want to discuss RAII implementation in C++, no problem.  We can stop now. 

For me, as a programmer, I would never accept the use of a programming technique without considering the implementation.  For example, I like OOP better than DOD, in prinicple.  But in practice, the implementation of OOP often has terrible performance.  My code usually ends up being a marriage of the two, focusing more on DOD principles where performance is critical.

Similarly, if unique_ptr had a 500% performance hit on dereferencing, nobody would use it.  Or, I certainly wouldn't.  That's not a principle thing, that's an implementation thing.  As you rightfully pointed out, it doesn't.  The implementation is important.

shared_ptr is also an implementation detail.  But there are legitimate concerns with the use of shared_ptr.  The advantages you list for shared_ptr are also cause for some significant concern.  These are highlighted in the article I linked.  It actually has a good section on resource manager-style use of shared_ptr, such as in your Thor implementation.

Thanks for sharing your thoughts on Thor.  Everything makes sense. 

A user of Thor is still choosing to use shared_ptr.  I would say that this decision is one that be made carefully, in light of both advantages and concerns with shared_ptr.  Again, I would argue there are valid arguments to be made both ways.  You may personally weight the evidence, and decide the pros may outweigh the cons.  I would hope that you at least recognize their are cons to be considered.  Again, these are highlighted in the article.

If you read it, and say, "I read the article, and found no cons to using smart_ptr", then I can try to point you in the right direction.
Title: Re: Yet another RAII discussion
Post by: Jabberwocky on February 12, 2016, 03:16:28 pm
I should add that this article is written by the Lead Software Engineer at Wargaming Seattle, (https://www.linkedin.com/in/seanmiddleditch) with an extensive history in the AAA gaming industry.  He also mentions consulting his peers in the industry on the topic. 

That doesn't mean he's necessarily correct.  I disagree with people in the industry all the time.  But it does mean he isn't some hack spouting off crap with no experience to back it up, as may have been the case with other articles you've dismissed.

In particular, I would draw your attention to these sections in his article:

... but ideally you'll want to read the whole thing.

Here's a link to the article again.
Dangers of std::shared_ptr (http://seanmiddleditch.com/dangers-of-stdshared_ptr/)
Title: Re: Yet another RAII discussion
Post by: Nexus on February 13, 2016, 09:24:37 pm
If someone else were to argue, "Listen man, I'd prefer to type more code than have some weird templated smart pointer black magic obscuring all my logic, and changing the established way C++ is written", I'd call that a good argument, too.
Generally I agree that more verbose approaches can make sense when they're simpler and less obscure (like it is the case with many Boost examples). In this case however, I'd say the abstraction of copyable smart pointers is really easy to understand. It isn't filled with surprises, as the pointer has value semantics and thus behaves exactly as one is used from C++ objects. If you say: "look, CopiedPtr<T> behaves exactly like T, except that it has some advantages of a pointer, like cheap move construction and ability to deal with polymorphic types", the code should be pretty straightforward to understand. One does not need to know how CopiedPtr<T> is implemented.

Of course, people who have never seen aurora::CopiedPtr might wonder what it's doing, but that can be amended by looking up its documentation or even just by a one-liner comment in the place it's used. Reading and understanding equivalent boilerplate code, including a separate enum type, may arguably take more time and could lead to questions.

Either way, nice work with the masterful smart pointer wizardry.
Thank you. In my opinion, copied pointers are definitely the most versatile and useful tool in the Aurora library; I've used them in loads of places already. It took me some iterations to get to the current design though, early versions were quite limited and less optimized.

But there are legitimate concerns with the use of shared_ptr. [...] I would hope that you at least recognize their are cons to be considered.
I think you misunderstood my opinion in this regard. I definitely agree that shared pointers have disadvantages and their use cases have to be considered carefully. I've also noted this in my article and mentioned here that I consider it overused. Still, the article you linked is definitely interesting to read, as it outlines some lesser known problems in detail.

So, it seems like we agree that RAII can greatly simplify and improve code, and abandoning it is primarily reasonable due to problems with concrete implementations, such as std::shared_ptr? :)
(and even in these cases, I'd argue that a different implementation can be more suitable than MMM...)
Title: Re: Yet another RAII discussion
Post by: Jabberwocky on February 14, 2016, 11:26:18 am
re:  aurora::CopiedPtr

Yep, good arguments.

There's no doubt that having to write some/all of a constructor, destructor, copy constructor, copy assignment operator, move constructor, and move assignment operator gets pretty burdensome.  I am highly sympathetic to simplifying this.

I recall reading an article (or maybe it was a video) by Jonathan Blow, who is a pretty popular and influential indie game developer.  He discussed the need for a new programming language for games.  He has many issues with C++ but these complexities addressed by aurora are one of them.  So it's not an insignificant problem.  It legitimately pushes some smart people away from C++.  Blow is actually working on a new language for games (https://inductive.no/jai/).  That link is off-topic, highly optional reading, but just figured I'd include it for those interested. 

Personally, I'd have to be dragged kicking and screaming from C++.  But I get people's complaints.  I would actually be interested in official C++ tackling this problem, similar to how your aurora stuff does.  If it were part of the standard, the "principle of least astonishment" argument (whether you agree with it or not) would completely disappear.

re:  C++ RAII

I definitely agree that shared pointers have disadvantages and their use cases have to be considered carefully. I've also noted this in my article and mentioned here that I consider it overused. Still, the article you linked is definitely interesting to read, as it outlines some lesser known problems in detail.

Cool.  Thanks.

So, it seems like we agree that RAII can greatly simplify and improve code, and abandoning it is primarily reasonable due to problems with concrete implementations, such as std::shared_ptr? :)

Yep.

This is where I am at.  If I liked the concrete implementation, I would try adapting RAII in my code.  I can't guarantee I wouldn't find other issues once I got my hands dirty.  But I would be sold on giving it a shot. 

_____________

Here's the broader picture.  There's a series of mental steps a person has to make to go from traditional manual mem management, to adapting RAII. 

1.  Syntax
It is very common to dislike the syntax, which I readily admit is a very minor issue.  But hey, coders will argue for hours on where to put curly braces.  So while I might mention it, I wouldn't pursue it as a serious argument against RAII.  But the first reaction is often an "ewww" one.  ;)

I'm just talking about the thought process, not trying to make arguments to debate here.

2.  Old vs. New
It's very common to require convincing, by oneself or others, that RAII is actually better, and if so, by how much.  Memory management and memory leaks are issues programmers have dealt with for a long time.  We've written a lot of software using our old, non-RAII tools and techniques.  We are competent with them.  We trust them.  They have worked well for many of us in the past.  Arguments telling us that they don't work just annoy us, because we believe they do.  It would be like someone telling you you should use C# because you can't trust programmers to work with memory, with or without RAII (both of which are susceptible to programming errors, although RAII less so).

This is why people will mention things like memory leak detectors.  I readily accept that there are reasonable arguments against these older techniques.  But obviously, the argument for RAII would be far stronger if these older techniques did not exist.  That's why they get brought up.  RAII is a replacement to existing techniques.  So you must examine both together.  It's a debate between the old and the new.  Personally, I believe it is wrong to frame the memory management argument as "the wrong way" and "the right way" (and doing so will likely alienate those who require convincing).  Rather, I believe it should be framed as the advantages and disadvantages of different tools.  Both advantages and disadvantages exist.  "Familiarity", "existing, functional code", and "established technique" being some arguments for manual management. 

For example, I chose to use CEGUI for my UI library in my current game.  There are a lot of other GUI choices.  Maybe some of them are even better.  I don't know.  But I have a huge familiarity with CEGUI.  I have a lot of existing code.  I have a highly efficient workflow.  I just simply know how it works.  So it's one less unknown piece of tech, one less big learning curve I have to deal with in my current game.

Maybe some other GUI libs do things a little better, in certain ways.  That alone would not be enough for me to change.  It would have to be a lot better.

I'm sure one day I will change to something other than C++.  Some fancy new language.  But when that day comes, it will take a lot of convincing.  I'm good with C++.  It works.  Same kind of thinking.

Again, not trying to make arguments.  Discussing thought process.  ;)

3.  Examining reasons not to adapt RAII.
This is where we hit the implementation, and smart pointer issues. 

As discussed above, many programmers have to be strongly convinced to abandon tried and true techniques for something new.  This is reasonable. 

I'm pretty good with my existing techniques.  Changing requires me to abandon those.  Learn new stuff.  Rework my existing code.  Likely deal with new kinds of bugs and other issues as I adapt and learn.  Take for example the difficulties associated with debugging non-released memory from shared pointers (memory I expect to have been released).  That's a different kind of debugging than I've ever had to do before.  I'm starting from scratch.

So, if I see definite problems in the implementation of C++ RAII, that may be enough to weaken the "strong convincing" I need to adapt it. 

That's why discussing these problems associated with shared_ptr are such an important part of the process.  Or, instead, discussing alternatives to shared_ptr that do not have these problems.

This is why I was so interested earlier in talking about shared_ptr alternatives, using a combination of unique_ptr and raw pointers.

That's a big wall of text.  But I hope what it achieves is to be more sympathetic to those who either decide not to adapt C++ RAII, or are somewhere along the stages of accepting it, but not quite there yet.

Thanks for the great discussion, Nexus.
Title: Re: Yet another RAII discussion
Post by: Mörkö on February 14, 2016, 07:23:17 pm
There are enough messy memory leaking C++ programs, and enough perfectly leak free C programs and RAII-less C++ programs to prove that smart pointers are not the definite solution to this set of problems.

And I suppose I have to mention the obvious: There are enough of the vice-versa case too that there is no question whether memory leaks are a problem for all levels of programmer. The C++ committee tried to fix it but the solution is nowhere near perfect and as has been shown to create its own new set of issues. It's up to everyone to decide whether the benefits of RAII outweigh the introduced downsides and there is no clear answer to that question as evidenced by the mere fact of debates of this nature.

Ultimately I think the issue is more of taste and style than of actual real practical importance. In my opinion, if you are going to use C++ then you might as well go all the way with templates, classes, RAII, OO paradigm etc. C++ is sold as being multi-paradigm, which might be technically true, but at the same time the language is hinting, implying, and seductively inviting you to write OO code. I'd say that limiting oneself to only a subset of C++ as some people advocate is a bad idea, because it's designed so that if you use one of the "nice" features then eventually you will want (need) to use all of them anyway and trying to resist that process will make your code a mess. If you don't want to use everything in C++, then just use C!
Title: Re: Yet another RAII discussion
Post by: Nexus on February 15, 2016, 12:59:02 pm
I recall reading an article (or maybe it was a video) by Jonathan Blow, who is a pretty popular and influential indie game developer.  He discussed the need for a new programming language for games.
Seems quite interesting, thanks for the resources!

Personally, I'd have to be dragged kicking and screaming from C++.  But I get people's complaints.  I would actually be interested in official C++ tackling this problem, similar to how your aurora stuff does.  If it were part of the standard, the "principle of least astonishment" argument (whether you agree with it or not) would completely disappear.

2.  Old vs. New
Yep, that's definitely a good point. You also see how C or Java programmers struggle to get rid of their habits when they learn C++. It's probably a quite common phenomenon that people don't want to leave the comfort zone as long as things work. But it's often worth the step, because it's an opportunity to get in touch with really cool new stuff. It also depends on the personality; some people are happy when things work and invest 100% their time into productive code, and others try to use every opportunity to learn new things. I think it helps greatly when one is interested in programming per se, not just the results thereof.

I think it's also one of the main reasons why C++ is still that popular today. There are many alternatives, and arguably languages with a similar application field that do several things better, because they don't have to carry all the historical burdens (D as an example). Yet, they apparently don't provide advantages crucial enough to justify a re-learning of an entire language, migration of code bases, rewrite of libraries or switch of communities.

Which is also a reason why I see potential in libraries like Aurora, Ponder (https://github.com/billyquith/ponder), range-v3 (https://github.com/ericniebler/range-v3) and other specialized language-level C++11/14 libraries. (I don't know that many yet). While not providing 100% the support of native language features, they make C++ much more comfortable to work with. Boost also contains many ingenious ideas, with a modernized and decoupled code base it would be really cool to work with.

Thanks for the great discussion, Nexus.
The same to you. It's amazing how this turned out :)

enough perfectly leak free C programs and RAII-less C++ programs to prove that smart pointers are not the definite solution to this set of problems.
That's not a proof, and it misses the point. We've discussed this in great detail already, you might want to read the thread carefully ;)

The C++ committee tried to fix it but the solution is nowhere near perfect and as has been shown to create its own new set of issues. It's up to everyone to decide whether the benefits of RAII outweigh the introduced downsides and there is no clear answer to that question as evidenced by the mere fact of debates of this nature.
That's a very vague paragraph without explanation. What RAII downsides? What "own set of issues"? (Remember we're talking about RAII itself, not shared_ptr or other undoubtedly problematic implementations).

Also, many people agree that C++ has the best resource management strategy in its family of programming languages. Java's resource handling for example is terrible. Yes, it has a GC, but that only serves memory. Every other resource must essentially be closed manually, like in the old days of C (finalizers are not reliable and thus effectively useless for resource deallocation). Things have become slightly less terrible with try-with-resources, but this idiom is still miles behind RAII, because it is limited to local variables and requires boilerplate code at call site (instead of inside the resource, i.e. destructors). And since exceptions are prevalent in Java (and try-catch clutters the code base, thanks to checked exceptions), exception safety is even more important. And without RAII, it's a mess. The try-finally approach is extremely tedious, because you must rewrite the rollback logic every time you use a resource, instead of once in the resource itself.

Deterministic scope-bound destruction is a remarkably effective concept. Languages like D have recognized this and even brought it a step further with the scope keyword. And use cases of RAII are not limited to resource management; it can be extended to any ownership-based attach/detach semantics (e.g. when this game character dies, automatically unregister it in other places, through a tracker object).

I'd say that limiting oneself to only a subset of C++ as some people advocate is a bad idea, because it's designed so that if you use one of the "nice" features then eventually you will want (need) to use all of them anyway and trying to resist that process will make your code a mess. If you don't want to use everything in C++, then just use C!
I disagree. Many features of C++ are obviously flawed, often because of their historical origins that don't fit well with modern paradigms. Some have even been deprecated or entirely removed from the standard. Because C++ is extremely complex, it's almost impossible to use every single feature; I find it much more reasonable to carefully choose and combine the well-designed features, rather than forcefully mixing everything the language has to offer into one big pot.

Ultimately I think the issue is more of taste and style than of actual real practical importance.
I find it important to have great discussions that allow developers to investigate new aspects of programming and become more efficient. Profound reasoning being denied by the "taste" compromise argument makes me sad.

Please have a look at my aurora::CopiedPtr example above and tell me how it has no practical importance whether I write 15 lines of code, or 300 while still being more constrained.
Title: Re: Yet another RAII discussion
Post by: eXpl0it3r on February 15, 2016, 02:14:31 pm
What I feel is kind of missing from the whole discussion is the clear distinction between RAII and smart pointers.

RAII is quite a bit older than smart pointers and it simply means that resources are bound to an object and will get properly deallocated at destruction of the object. So given what Jabberwocky kind of mentioned in previous posts what he's doing with his self-knitted classes with destructors and copy constructors IS in fact doing RAII - allocate the object in the constructor, deallocate it in the destructor - but it introduces higher risks of getting something wrong (deep copy, proper deallocation) and usually generates more boilerplate code.

This is where smart pointers come in. unique_ptr can essentially be seen with the main purpose of encapsulating a resource for proper RAII handling. It has pretty much always no overhead and prevents quite a few of the risks of writing the boilerplate code yourself. shared_ptr and others do come with some overhead (and potential other issues), but their main purpose is not to provide an RAII encapsulation, as such the argument one seems to hear a lot around the internet of "shared_ptrs have an overhead (and potential other issues) thus RAII is bad" is just plain wrong.


One of the point I think why Nexus, I and others kind of get annoyed whenever a new RAII discussion comes up is, that the topic is not looked at in a more subjective way, while trying to give advice to other people.

You are used to a certain way of coding, that is fine and you can keep doing so, however is your way of coding really the least risky way and the easiest to deal with? As a top athlete, is the workout routine you've figured out over multiple years really the best workout routine for someone just starting out as an athlete? Should one always start with the (arguably) max performance/high risk situation or could other ways fit someone else better? What do experts on this field advice?

My point being: When giving advice to other people, you shouldn't assume everyone is as "clever" as you are and not every has the need for these 2ms performance gain (if any). Instead you should give advice on things that have low risk of using it wrong and are easy to maintain.


Disclaimer: The "you" here is used in a general sense and not specific to anyone.