Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: Why RAII rocks  (Read 15150 times)

0 Members and 1 Guest are viewing this topic.

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6286
  • Thor Developer
    • View Profile
    • Bromeon
Why RAII rocks
« on: October 09, 2012, 07:22:06 am »
This post continues the discussion from a project thread.

In order to get an overview of the topic, have a look at my article about RAII. 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.
« Last Edit: January 23, 2014, 02:20:45 pm by Nexus »
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

Qix

  • Full Member
  • ***
  • Posts: 139
  • I am Qix!
    • View Profile
    • Natoga Technologies
Re: Why RAII rocks
« Reply #1 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.
~ Qix
Creator of Rippl Studio
Code: [Select]
<danharibo> iostream: I don't do enough drugs to think that's a good idea.

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6286
  • Thor Developer
    • View Profile
    • Bromeon
Re: Why RAII rocks
« Reply #2 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?
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

Qix

  • Full Member
  • ***
  • Posts: 139
  • I am Qix!
    • View Profile
    • Natoga Technologies
Re: Why RAII rocks
« Reply #3 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.
~ Qix
Creator of Rippl Studio
Code: [Select]
<danharibo> iostream: I don't do enough drugs to think that's a good idea.

binary1248

  • SFML Team
  • Hero Member
  • *****
  • Posts: 1405
  • I am awesome.
    • View Profile
    • The server that really shouldn't be running
Re: Why RAII rocks
« Reply #4 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.
SFGUI # SFNUL # GLS # Wyrm <- Why do I waste my time on such a useless project? Because I am awesome (first meaning).

Qix

  • Full Member
  • ***
  • Posts: 139
  • I am Qix!
    • View Profile
    • Natoga Technologies
Re: Why RAII rocks
« Reply #5 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.
« Last Edit: October 10, 2012, 01:43:44 am by Qix »
~ Qix
Creator of Rippl Studio
Code: [Select]
<danharibo> iostream: I don't do enough drugs to think that's a good idea.

Tank

  • SFML Team
  • Hero Member
  • *****
  • Posts: 1486
    • View Profile
    • Blog
    • Email
Re: Why RAII rocks
« Reply #6 on: October 10, 2012, 09:38:18 am »
So you prefer unsafe behavior over safety?

Qix

  • Full Member
  • ***
  • Posts: 139
  • I am Qix!
    • View Profile
    • Natoga Technologies
Re: Why RAII rocks
« Reply #7 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.
~ Qix
Creator of Rippl Studio
Code: [Select]
<danharibo> iostream: I don't do enough drugs to think that's a good idea.

eXpl0it3r

  • SFML Team
  • Hero Member
  • *****
  • Posts: 10801
    • View Profile
    • development blog
    • Email
Re: Why RAII rocks
« Reply #8 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). ;)
Official FAQ: https://www.sfml-dev.org/faq.php
Official Discord Server: https://discord.gg/nr4X7Fh
——————————————————————
Dev Blog: https://duerrenberger.dev/blog/

binary1248

  • SFML Team
  • Hero Member
  • *****
  • Posts: 1405
  • I am awesome.
    • View Profile
    • The server that really shouldn't be running
Re: Why RAII rocks
« Reply #9 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.
SFGUI # SFNUL # GLS # Wyrm <- Why do I waste my time on such a useless project? Because I am awesome (first meaning).

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6286
  • Thor Developer
    • View Profile
    • Bromeon
Re: Why RAII rocks
« Reply #10 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.
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6286
  • Thor Developer
    • View Profile
    • Bromeon
Re: Why RAII rocks
« Reply #11 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 :)
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

Qix

  • Full Member
  • ***
  • Posts: 139
  • I am Qix!
    • View Profile
    • Natoga Technologies
Re: Why RAII rocks
« Reply #12 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.
« Last Edit: October 10, 2012, 08:07:54 pm by Qix »
~ Qix
Creator of Rippl Studio
Code: [Select]
<danharibo> iostream: I don't do enough drugs to think that's a good idea.

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6286
  • Thor Developer
    • View Profile
    • Bromeon
Re: Why RAII rocks
« Reply #13 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?
« Last Edit: October 10, 2012, 09:01:49 pm by Nexus »
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

Qix

  • Full Member
  • ***
  • Posts: 139
  • I am Qix!
    • View Profile
    • Natoga Technologies
Re: Why RAII rocks
« Reply #14 on: October 10, 2012, 09:02:21 pm »
But they aren't for free. They still introduce overhead with tracking objects!
~ Qix
Creator of Rippl Studio
Code: [Select]
<danharibo> iostream: I don't do enough drugs to think that's a good idea.