How to use RAII, Constructors and Exceptions

Asked

Viewed 47 times

2

I’m new to C++ and recently came across something that I can’t quite understand, which is: in my research, I saw that RAII is a suitable technique to be used when you need to acquire a resource in the constructor and destroy automatically in the destructor.

In these same researches, I also learned that RAII is widely used with exceptions and it is often stated that exceptions are the main way to deal with errors in constructors since they do not return value.

However, in my research involving a dozen sites and a few books, I did not see an example involving a RAII, in which the builder made an exception. And when I tried to do it myself, things just didn’t work out.

Below, my attempts (I will use as an example resource acquired in the builder the pointer to the struct SDL_Surface* of the SDL library, which I am most familiar with):


#include <SDL.h>
#include <SDL_image.h>
#include <iostream>

class Surface {

private:
    SDL_Surface* surface_;
public:
    Surface(std::string path) {

        surface_ = IMG_Load(path.c_str());
        if (!surface_) {
            throw std::runtime_error("unable to load file.\n");
        }
    }
    ~Surface() {
        if (surface_) {
            SDL_FreeSurface(surface_);
        }
    }
    SDL_Surface* getSurface() {
        return surface_;
    }
};

int main(int argc, char* args[]) {


    SDL_Init(SDL_INIT_VIDEO);

    std::string file_path = "foo.png";

    try {
        Surface foo_surface(file_path);     
    }
    catch (std::runtime_error &e) {
        std::cerr << "Caught a runtime_error exception: "
            << e.what() << '\n';

    }

    //agora ao tentar usar foo_surface, não é possível pois a variável saiu do escopo ao
    //final do bloco try    
    int largura = foo_surface.getSurface()->h;
    int altura = foo_surface.getSurface()->w;

    return 0;
}

I mean, on this first attempt, things didn’t work out because foo_surface lost scope at the end of the block try{} and, of course, the compiler complains: “identifier ‘foo_surface’ is undefined”.

To try to circumvent this, a second attempt with some modifications (displayed only the function main()):



int main(int argc, char* args[]) {


    SDL_Init(SDL_INIT_VIDEO);

    std::string file_path = "foo.png";
    Surface* foo_surface = nullptr;

    try {
        foo_surface = new Surface("foo.png");       
    }
    catch (std::runtime_error &e) {
        std::cerr << "Caught a runtime_error exception: "
            << e.what() << '\n';

        return -1;  
    }

    //até aqui as coisas funcionam bem, mas...
    int largura = foo_surface->getSurface()->h;
    int altura = foo_surface->getSurface()->w;

    //eu preciso deletar foo_surface no final do escopo.
    delete foo_surface;

    return 0;
}

The code works (and compiles), but the problem is that now I need to delete foo_surface at the end of the scope. And why is that a problem? Like, the only reason I created a class was the possibility of the destructor automatically releasing the memory when the object lost the scope, with the need to delete the object I lose my original motivation and obviously if to use delete, it is much easier to simply abandon automatic memory management and stick to the solution without using class, constructor, destructor and exception, which consists of:


int main(int argc, char* args[]) {

    SDL_Init(SDL_INIT_VIDEO);

    SDL_Surface* foo_surface = IMG_Load("foo.png");
    if (!foo_surface) {
        std::cerr << "Unable to load image.\n";
        return -1;
    }

    int largura = foo_surface ->w;
    int altura = foo_surface->h;

    SDL_FreeSurface(foo_surface);

    return 0;
}

Anyway, as I read a lot that RAII and exceptions are commonly used together, as I saw no practical example of this, and as my attempt to use both things together simply did not work as stated above, the impression I have is that I’m letting something very basic escape.

So my doubts are:

  1. my approaches were correct or I’m making a very big mistake of beginner?
  2. And how to reconcile the automatic memory management provided by RAII with exceptions released by the constructor, given the block scope problem try{};

Finally, I’m not a programmer, in fact, I started studying C++ about a year ago, out of simple curiosity and (I loved programming), so I apologize for the numerous conceptual, terminological and coding errors I must have made in this question.

1 answer

1


The most RAII for your second case, possibly is the use of smart pointers3, as std::unique_ptr and std::share_ptr, but it seems to me that you’re afraid to use Try’s body.

How should you avoid heap allocation4, the best way out is something like:

try {
    // cria
    Surface foo_surface(file_path);

    // aqui eh garantido o uso do recurso adquirido
    int largura = foo_surface.getSurface()->h;
    int altura = foo_surface.getSurface()->w;

    // faco algo legal com a minha largura e altura (adquiridas)
}
catch (std::runtime_error &e) {
    std::cerr << "Caught a runtime_error exception: " << e.what() << '\n';
}

For this it is good to keep in mind that exceptions before fired, because were outside the try, now will be taken and treated equal to the initialization/acquisition error, and no longer fired.

The solution is to have a strong typography of exceptions, or at least:

try {/*cria e usa*/}
catch (InitError &) {/*erro iniciando*/}
catch(ExecError &) {/*erro usando*/}
catch(...) {/*erro nao esperado*/}

But use the body of Try freely, of course, trying to stick to the scope of the acquired resource (created object):

try {
    /*cria*/ 
    try { /*usa*/ }catch(...){/*erro usando*/}
}
catch(...) {/*erro iniciando*/}
  • 1

    Got it. Your answer makes perfect sense and solved other problems. I mean, even in the second example the only treatment I could give for the exception was the closure of the program, otherwise, foo_surface could not have been initialized and an error would occur on the line int largura = foo_surface ->w; and on the next line. Well, I accepted the answer, I think I understood what I was missing, I gave upvote too, but it doesn’t show up because I don’t have a reputation for it. Vlw.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.