SFML community forums

General => General discussions => Topic started by: Nexus on October 09, 2012, 07:22:06 am

Title: Why RAII rocks
Post by: Nexus on October 09, 2012, 07:22:06 am
This post continues the discussion from a project thread (http://en.sfml-dev.org/forums/index.php?topic=8652).

In order to get an overview of the topic, have a look at my article about RAII (http://www.bromeon.ch/articles/raii.html). The article gathers all the arguments and counter-arguments from this thread, and investigates the fundamental problem of memory management in a detailed manner.


Yes there is; stack vs. heap. You're limited on stack space.
You can use the heap (or freestore) without manual memory management, take a look at the RAII idiom.

new and delete cause headaches even if used correctly, just try to implement a function with several dynamic allocations and multiple return paths/exceptions.
Title: Re: Why RAII rocks
Post by: Qix on October 09, 2012, 10:38:44 am
That generally for safe stack unwinding and lifetime guarantees when exceptions or other unforseen errors are thrown when there is, indeed, an error.

You aren't avoiding new/delete, though.
Title: Re: Why RAII rocks
Post by: Nexus on October 09, 2012, 05:19:07 pm
Even in absence of exceptions, there are many adaptions necessary when the objects are deallocated manually. With exceptions, it becomes extremely difficult to handle everything correctly, and once you achieve that, code is completely unreadable.

And new/delete needn't be avoided completely in C++, but in my opinion, these operators should mainly be used behind encapsulated higher-level objects, such as containers or smart pointers. Rather, I want to convince people that it is a bad idea to use manual memory management in normal end-user code.

May I ask why you never use smart pointers? Have you already taken a look at std::unique_ptr?
Title: Re: Why RAII rocks
Post by: Qix on October 09, 2012, 11:36:34 pm
Yes - it's for cleanliness of my code as well as control. It's a personal preference; I know exactly where my objects are and when their lifetime begins and ends. There is no need for shared pointers simply because I'm responsible with my pointers.

Plus, to me it's unreadable using smart pointers. It makes much more sense using raw pointers.

Again, personal preference.
Title: Re: Why RAII rocks
Post by: binary1248 on October 10, 2012, 12:41:04 am
No disrespect, but there is a point where software projects get so large and complex that it is not possible for any human, no matter how skilled they are to keep the overview over the lifetime of every single object throughout the code. I also thought the same as you when I started out and as soon as I had to work with more than 20000 lines of code I just gave up and started using smart pointers. This is also not a result of bad design or anything of the like. I guess you just haven't reached the break even point yet.
Title: Re: Why RAII rocks
Post by: Qix on October 10, 2012, 01:40:58 am
No disrespect, but there is a point where software projects get so large and complex that it is not possible for any human, no matter how skilled they are to keep the overview over the lifetime of every single object throughout the code. I also thought the same as you when I started out and as soon as I had to work with more than 20000 lines of code I just gave up and started using smart pointers. This is also not a result of bad design or anything of the like. I guess you just haven't reached the break even point yet.

Please don't speak like you know me ^_^

There are certain situations where smart pointers are useful, but for me, as a pure personal preference, smart pointers make little sense procedurally and provide very little advantage.
Title: Re: Why RAII rocks
Post by: Tank on October 10, 2012, 09:38:18 am
So you prefer unsafe behavior over safety?
Title: Re: Why RAII rocks
Post by: Qix on October 10, 2012, 04:43:12 pm
It's only unsafe if you use them incorrectly.

Smart-pointers are just a fad. You can use them safely if you use raw pointers how they were intended to be used, and if you are certain you use new/delete appropriately.
Title: Re: Why RAII rocks
Post by: eXpl0it3r on October 10, 2012, 05:19:08 pm
It's only unsafe if you use them incorrectly.

Smart-pointers are just a fad. You can use them safely if you use raw pointers how they were intended to be used, and if you are certain you use new/delete appropriately.
Even in absence of exceptions, there are many adaptions necessary when the objects are deallocated manually. With exceptions, it becomes extremely difficult to handle everything correctly, and once you achieve that, code is completely unreadable.
As Nexus had already stated, you just can't make sure that nothing goes wrong in between your 'safe' new and delete calls and thus you have to live in constant fear for memory leaks. With smart pointers/RAII objects the lifetime is well defined, i.e. as soon as the scope is left. No matter what happens between point A and B, if the scope is left the resource will get released.

It would be interesting to see some complex code of yours which in your opinion is safe. Maybe it really is just a feeling of yours that things are safe while they are not (I don't know so I can only assume and guess things). ;)
Title: Re: Why RAII rocks
Post by: binary1248 on October 10, 2012, 06:06:13 pm
It's only unsafe if you use them incorrectly.

Smart-pointers are just a fad. You can use them safely if you use raw pointers how they were intended to be used, and if you are certain you use new/delete appropriately.
Safety was never a consideration when the concept of pointers came around, as such you can't say that using pointers as intended leads to safety. In C mallocing something and not freeing it is also something that should be avoided (most of the time at least). Before the advent of virtual memory, you didn't even have to allocate and free memory because you had access to the physical bytes yourself. These corner cases show that proper usage isn't a matter of doing the same things all the time. Sometimes you just have to allocate, sometimes you just have to free. This is why safety only became prominent in modern C++ where development cost starts to matter.

The idea of safety is to protect against mistakes, not to prevent them. Your reasoning is based on circular logic: if you use new/delete in a safe way, it is safe to use. This is not the idea of safety. You also have to consider the other cases when you don't use them in a safe way. Smart pointers are very very forgiving in those cases. You will either notice at compile time that something isn't right, or you will (in most implementations) receive some form of notification that you are using them wrongly during runtime. Raw pointers are very subtle when it comes to this. The best scenario would be your program crashing if you make a mistake. Even in that case without any meaningful message. Even worse, if you don't periodically run leak checks because you are confident that your program doesn't leak, it will randomly crash after different amounts of time on different systems depending on how fast memory can get exhausted. Or the most expensive variant: you access memory locations that don't contain the data you expect and get wrong values from some calculation.

Please don't remove the railing from the bridge because you only expect sober people to cross it.
Title: Re: Why RAII rocks
Post by: Nexus on October 10, 2012, 07:51:02 pm
Quote from: Qix
There is no need for shared pointers simply because I'm responsible with my pointers.
As stated in the other thread, you rarely need shared pointers, unique pointers cover most cases.

Quote from: Qix
Yes - it's for cleanliness of my code as well as control. It's a personal preference; I know exactly where my objects are and when their lifetime begins and ends.
You know that also with smart pointers: Lifetime begins at initialization and ends at the end of scope. In contrast to raw pointers, smart pointers guarantee that the objects are actually destroyed, even if you don't explicitely advise them to do so.

Quote from: Qix
Plus, to me it's unreadable using smart pointers. It makes much more sense using raw pointers.
Only the declaration may be less readable because you write unique_ptr<T> instead of T*. However, you need much more code for the correct deallocation. As a consequence, in more complex examples, the important tasks of the function tend to get lost between cleanup code.
Title: Re: Why RAII rocks
Post by: Nexus on October 10, 2012, 07:56:00 pm
Okay. Simple example, our task is to avoid memory leaks:
int unsafe()
{
    A* a = new A;

    if (a->f())
    {
        B* b = new B;
        if (b->f())
            return b->g();
        else
            return a->g();
    }
   
    return 0;
}

First, assume there are no exceptions. Let's write the corresponding deallocation code:
int unsafe2()
{
    A* a = new A;

    if (a->f())
    {
        B* b = new B;

        if (b->f())
        {
            int r = b->g();
            delete b;
            delete a;
            return r;
        }
        else
        {
            int r = a->g();
            delete b;
            delete a;
            return r;
        }
    }

    delete a;
    return 0;
}

Not only do we need to insert delete statements, the manual deallocation forces us to break the code flow and store return values in variables instead of returning them directly.

Now we assume that constructors may fail, and attempt to make the code exception-safe and exception-neutral:
int unsafe3()
{
    A* a = new A;

    if (a->f())
    {
        B* b;
        try
        {
            b = new B;
        }
        catch (...)
        {
            delete a;
            throw;
        }

        if (b->f())
        {
            int r = b->g();
            delete b;
            delete a;
            return r;
        }
        else
        {
            int r = a->g();
            delete b;
            delete a;
            return r;
        }
    }

    delete a;
    return 0;
}

We have doubled the original amount of code -- only for deallocation and error handling, we haven't introduced new functionality. And the code is not even safe yet, since the other functions might also throw exceptions. I think you can imagine how complexity further explodes if you implement that. Code becomes totally unreadable, the actual function logic is lost in boilerplate code.

Note also that we use only two dynamic allocations that require such a complicated logic. Imagine how it might look like for 5, or 10. Furthermore, I have only talked about memory leaks. Problems like dangling pointers or double deletes exist, too, just in case life becomes boring.

Now the good news: C++ offers us RAII, a simple yet extremely powerful idiom to manage resources automatically. RAII is not limited to memory, it handles resources in general. What our code looks like with the use of smart pointers:
int safe()
{
    std::unique_ptr<A> a(new A);

    if (a->f())
    {
        std::unique_ptr<B> b(new B);
        if (b->f())
            return b->g();
        else
            return a->g();
    }
   
    return 0;
}

Our code has the same length as the first snippet and is completely safe. We can introduce further return or throw statements without touching memory management. Also, we don't get into trouble if one of the called constructors or functions begins to use exceptions. Code remains very clean, and we have zero performance overhead. Neither speed, nor memory.

As you see, advantages of RAII are far from little. In fact, I don't see a reason not to use basic smart pointers. I understand your personal preference, as you may be used to manual memory management and it worked well so far, but I hope I could show you its hidden problems, and how easy your life could be with RAII :)
Title: Re: Why RAII rocks
Post by: Qix on October 10, 2012, 08:05:37 pm
While this is all true, when dereferencing smart pointers or god forbid when using shared pointers, there is indeed overhead. When used incorrectly, shared_ptr can arguably cause more of a headache than raw pointers.

I must correct something I said in the original thread; I do use smart pointers - sparingly and usually only  early on in a container's lifetime. This is because I design my classes with well defined lifetimes, and know that when interruption of execution occurs, somewhere down the line the code will be destructed safely.

For my own preference, smart pointers are only used when a program-wide or potentially unsafe (throwable) resource is created. However, when using code that never throws, and that doesn't prematurely return (like in your examples) there is absolutely no need to use smart pointers.

The majority of my gripe with smart pointers is that people seem to view them as the 'wonder drug' of resource management; when, in fact, a lot of newbies to programming are thrown with the concept of "smart pointers" before they understand what raw pointers even do and end up (incorrectly) using shared_ptr everywhere and then later complain when their program is ungodly slow.

I'm just arguing that smart pointers, for me, are not an excuse to be stupid or lazy with code. If I know exactly what is being performed, smart pointers are overkill. There are no rules or guidelines against mixing raw pointers and smart pointers.

It's also notable to distinguish between a smart pointer and RAII; smart pointers are an implementation or adaptation of the RAII mindset, while RAII in and of itself is just a philosophy. You're not avoiding new/delete at all; you're simply encapsulating it. Another gripe is that programmers falsely assume smart pointers will get rid of allocation through new/delete altogether.

What my generally negative disposition to smart pointers is is that they are misused; a lot. A lot of the time programmers use them lazily when their fundamental design of the program is flawed. A lot of the time they use them without knowing how they really work.

The whole spawn of this thread was the suggestion for a generally disgruntled/'always right' programmer creating a giant engine to use smart pointers. I objected generally because, speculating from their code samples and their overall program design, smart pointers wouldn't improve their existing design or code at all. It's understandable to suggest the consideration of smart pointers, but that project was beyond the point of return.
Title: Re: Why RAII rocks
Post by: Nexus on October 10, 2012, 08:53:42 pm
when dereferencing smart pointers [...] there is indeed overhead.
No, smart pointers usually store the pointer directly, and the function call of operator* can be inlined.

Quote from: Qix
What my generally negative disposition to smart pointers is is that they are misused; a lot. [...] A lot of the time they use them without knowing how they really work.
This argument applies even more to manual new/delete. Many developers, even experienced ones, make mistakes in memory management. I agree that shared_ptr is heavy and doesn't liberate people of learning how new/delete work. Abuse however is not a general argument against a feature. It happens also in other areas, just take a look at how often inheritance is used incorrectly in C++.

Quote from: Qix
For my own preference, smart pointers are only used when a program-wide or potentially unsafe (throwable) resource is created. However, when using code that never throws, and that doesn't prematurely return (like in your examples) there is absolutely no need to use smart pointers. [...] If I know exactly what is being performed, smart pointers are overkill.
Why overkill? Unique pointers don't introduce overhead or more effort. On the contrary, they make code simpler.

You never know if you do not change the behavior one day and introduce branches/exceptions anywhere. Even less if you work in a team. What is the point of risking errors if you can eliminate them for free?
Title: Re: Why RAII rocks
Post by: Qix on October 10, 2012, 09:02:21 pm
But they aren't for free. They still introduce overhead with tracking objects!
Title: Re: Why RAII rocks
Post by: Nexus on October 10, 2012, 09:06:30 pm
But they aren't for free. They still introduce overhead with tracking objects!
What do you mean exactly?

shared_ptr has big overhead, as a result of its reference counting, the dynamic deleters and thread-safety.

unique_ptr has no overhead. It is a simple wrapper around a raw pointer, with support for ownership transfer (using C++11 move semantics).
Title: Re: Why RAII rocks
Post by: Qix on October 10, 2012, 09:10:47 pm
Overhead isn't just performance, although it is one of the more important facets of it.

There is also a memory cost. While it isn't significant, any smart pointer creates a tracking object; that is, an object (wrapper) to hold the raw pointer itself. Depending on the implementation, this may include a vtable as well (which probably isn't the case with STL or Boost).

That's grasping at straws though. Depending on what you do with the smart pointer, there is overhead. Only in its most basic use is it close to being free.
Title: Re: Why RAII rocks
Post by: cire on October 10, 2012, 09:33:12 pm
Quote
While it isn't significant, any smart pointer creates a tracking object; that is, an object (wrapper) to hold the raw pointer itself.

#include <iostream>
#include <memory>

int main()
{
    std::unique_ptr<int[]> pointer(new int[32]) ;

    std::cout << "Size of pointer: " << sizeof(pointer)
              << "\nSize of int*: " <<  sizeof (int*) << '\n' ;
}

Output:
Size of pointer: 4
Size of int*: 4


Gee.  Where did they hide that tracking object?
Title: Re: Why RAII rocks
Post by: Qix on October 10, 2012, 10:21:14 pm
Right there. That is still an object. The size is the same, and yes it's only holding the pointer destination; however, you can allocate memory in the heap without needing to hold the pointer. It obviously becomes useless, but I'm saying, there is always overhead. Of course, anything but a shared_ptr (to my knowledge) will only create a 4/8 byte pointer in memory.
Title: Re: Why RAII rocks
Post by: Laurent on October 10, 2012, 10:37:21 pm
You guys talk a lot but this discussion isn't going anywhere :P

Let's summarize things. Qix is of course wrong (*) but he really likes the way he's doing things, and since he's not a beginner you won't convince him to change his habits.

(*) Smart pointers cause no overhead, thanks to inlining. It's just syntactic sugar and automatic behaviours that you would write anyway, the compiled code is the same. What they do, they do it better than any user code. If you don't think so please send a patch to the development team of your standard C++ library. And if you had a strong argument against them, you would already have provided a use case that demonstrates your point of view.

So please admit that your point of view is purely a personal taste, and let's stop this discussion that could make other people think that smart pointers can be bad. The only thing to say is "use them" -- if you need the features they provide, of course.
Title: Re: Why RAII rocks
Post by: Qix on October 10, 2012, 10:53:08 pm
You guys talk a lot but this discussion isn't going anywhere :P

Let's summarize things. Qix is of course wrong (*) but he really likes the way he's doing things, and since he's not a beginner you won't convince him to change his habits.

(*) Smart pointers cause no overhead, thanks to inlining. It's just syntactic sugar and automatic behaviours that you would write anyway, the compiled code is the same. What they do, they do it better than any user code. If you don't think so please send a patch to the development team of your standard C++ library. And if you had a strong argument against them, you would already have provided a use case that demonstrates your point of view.

So please admit that your point of view is purely a personal taste, and let's stop this discussion that could make other people think that smart pointers can be bad. The only thing to say is "use them" -- if you need the features they provide, of course.

I never said they were bad, but instead often used incorrectly; I don't have to admit anything because I said from the start that not using them is a personal preference; lastly, I'm not wrong, just providing another point of view.

Sure, most types of smart pointers are almost free (nothing is ever free, laurent; you know this) and I even said saying smart pointers (with the slim exception of shared_ptr) are not 100% free is grasping straws to begin with. However, I can't ignore that there is some extra baggage that comes along when applied to a large scale. It goes back to the 'wonder drug' phenomena.
Title: Re: Why RAII rocks
Post by: Laurent on October 10, 2012, 11:13:51 pm
I'm just trying to figure out where this discussion goes.

Quote
I never said they were bad, but instead often used incorrectly
I don't know if it's "often", but yes, they can be used incorrectly, like... anything else.
And someone who can write equally safe and performant code using raw pointers, is very unlikely to use them incorrectly anyway. Because it's much harder to write the same code with raw pointers.

Quote
I don't have to admit anything because I said from the start that not using them is a personal preference
So maybe it doesn't deserve such a discussion? If you know the advantages of RAII and smart pointers, but prefer to not use them, what else should be said?

Quote
I'm not wrong, just providing another point of view.
Sorry for that. I thought you were trying to prove that writing your own code based on raw pointers was better than using the standard smart pointers.

Quote
nothing is ever free, laurent; you know this
The classes we are talking about are free. And we are just waiting for more detailed and stronger arguments from you on this point :P

So, again, what's the point of this thread? (if you just want to continue what you were doing, then just tell me and I'll leave the discussion, I promise).
Title: Re: Why RAII rocks
Post by: Qix on October 11, 2012, 12:22:22 am
I wasn't the one who started this discussion.