Is it possible to do RAII without exceptions?
Asked Answered
S

2

11

If the resource one requires to acquire in the construction of an object can fail is it possible to do RAII, if exceptions are prohibited by a local coding standard?

If so, what is the canonical manner to handle the failure of resource acquisition in this case?

Soilure answered 23/10, 2015 at 11:10 Comment(9)
When I can't use exceptions for whatever reason I end up creating Initialization methods for objects that can fail construction. So the constructor would not do anything other than trivial setup. Then I can have an Init method that does the actual resource acquisition and returns true/false accordingly.Veratridine
Doesn't your guidelines say anything about situations like that? It is a very common C++ idiom after all. And if the guidelines allow it, you could always use a system similar to the standard I/O library and its streams, e.g. that the streams can be used in conditions to check for errors, or like the file streams having an is_open function.Tainataint
@JoachimPileborg I don't think it was considered in the guidelines, they may well require updating (though it's an embedded realtime system, and exceptions are not liked!) Regardless, my feeling is that it isn't possible, so just for the sake of this question I'd like my feeling confirmed! :-)Soilure
@MohamadElghawi That would be the way I would do things, but that breaks RAII.Soilure
@MohamadElghawi, having an initialisation method is not RAII.Howdy
Of course, if exceptions are not allowed then there's nobody around to catch one. So it doesn't matter at all that destructor won't run, the program is dead anyway.Tailspin
@Soilure Well you could use factory methods to group the creation and initialization together in one method call so an object cannot be used in an invalid state.Veratridine
@Soilure It's not ideal because in an ideal RAII implementation the constructor would throw if it cannot establish all class invariants. With factory methods you would rely on the factory method to establish all invariants of a class by first creating an instance then calling an initialization method. The best you can do is to return a nullptr on failure.Veratridine
@Soilure You may able to convince your colleagues that exceptions are not that bad at all. Most people associate a performance cost with them, but this is not true for years. Nowadays, exceptions are basically cost-free until they are actually thrown, and that should happen - well - exceptionally rarely. If you search for c++ zero-cost exceptions, you will find references that may help you.Durwood
D
9

I would not go with the invalid object approach in general because I would consider this as bad design. After construction, the object must be in a state where the invariants are established (that is the only purpose a constructor should serve). Consider a class strange_vector implementing something like std::vector, but after calling strange_vector<int>(10, 0), the object would be in an unusable state because the allocation failed.

Instead I would declare the constructors private and use a factory method which returns an optional:

class file
{
public:
    ~file() {fclose(m_file);}

    static std::optional<file> open(std::string const& filename)
    {
        auto f = fopen(filename.c_str(), "r");
        if (f)
        {
            return std::make_optional<file>(f);
        }
        else
        {
            return std::nullopt;
        }
    }

private:
    file(FILE* file);
    FILE* m_file;
};

One of the biggest benefits of exception handling is (besides decoupling error handling and normal code path) that you cannot ignore them accidentally. If you want that, you could create your own class similar to optional that, when not initialized with a valid object, logs an error message and terminates your program (or any other reasonable error handling). I think there is a talk from A. Alexandrescu about systematic error handling where he implements a class Expected<T> that contains either a value of type T or an exception. You could use this a basis and instead of the exception add your error handling there.

std::optional is not part of the standard yet, but you can easily get implementations either as part of recent compilers, in boost, or in other libraries.

Durwood answered 23/10, 2015 at 11:47 Comment(1)
You mixed local f and member m_file up, didn't you?Apprehend
G
0

You can always create an bool valid(void) method. The constructor can then set the appriopriate condition and in your code, you can check after construction wether this worked or not.

class foo
{
public:
    foo(const char *Filename)
    {
        mValid = false;

        if(fopen(Filename) == NULL)
           return;

        mValid = true;
    }

    bool valid(void) { return mValid; }

    private:
        bool mValid;
};

void myfunc(void)
{
    foo fl("myfile");
    if(!fl.valid())
    {
        printf("Error\n");
        return;
    }
}
Granulation answered 23/10, 2015 at 11:28 Comment(3)
I really don't like this approach. What should happen if I call a method of foo which uses the file, but initialization has failed? Constructors which do not established the object's invariants are a no-go.Durwood
If you call a method that uses foo it should validate internally that the file is indeed valid. Well, it's ugly, but at least it guarantuees that the object itself works correct.Granulation
That adds checking for every method call. I am not sure that the OP wants this in his embedded domain, and I don't think that I would want it in general, e.g. when a vector class with this approach which checks for every access...Durwood

© 2022 - 2024 — McMap. All rights reserved.