简体   繁体   中英

Best practices for using `std::error_code`

I am currently building an embedded system and use a modern C++ compiler. While I could technically fit exception handling in the given resources (ARM7, more than 10M RAM), I don't think exceptions are the right tool for something like this and using exceptions requires RTTI, which in turn results in code bloat.

To stay C++-ish anyway I want to use std::error_code (or similar with more data) because I do like the concept.

However, there does not seem to be any consenus on how to actually use them. I have seen at least four different ways of passing them between function calls, two of them with multiple semantics.

  1. Passing by pointer as an argument

    void somefunction(Args..., std::error_code* error);

    This is the way I have not seen that often and the one I dislike the most. It leaves the return type fully available and (often, but not always) passing nullptr resulted in normal throwing behaviour.

  2. Passing by reference as an argument

    void somefunction(Args..., std::error_code& error);

    This is the one I prefer. It leaves returnvalue fully available and makes clear that the error_code is not optional.

  3. Returning it by value

    std::error_code somefunction(Ret& out <= if used, Args...);

    I have seen this one quite often but don't really like it that much, as it uses up your return value and I generally don't like “out parameters” unless there's no way around them.

  4. Returning a std::variant<Ret, std::error_code>

     std::variant<Ret, std::error_code> somefunction(Args...);

    This one allows for a return value, but makes accessing both value and error harder. Also, it makes code calling the function more verbose.

Semantics

I have seen both way 1 and 2 with different semantics, if the error_code is passed.

  • Clear at start and set on error
  • Only set on error
  • Return right at start if the error_code is “set”

The last way is pretty good if you want to reduce error checking in the calling code. As you can just pass one error_code to multiple functions without checking in between and everything after the first error will not execute, similar to how exceptions would do it.

I personally do prefer way 2 with checking and returning, however I might be biased.

Is there some recommended / generally accepted way to do it?

Ok, this is no complete answer and actually not perfectly on topic because I am not aware of a standard way to do this. But I once saw a nifty little trick to make error codes harder to misuse. Consider the following code:

struct MyEC {
    MyEC() {}
    MyEC(MyEC && other) : parent(&other) {
        // Maybe log and or abort if other is not checked
        other.checked = false;
    }
    // Delete other constructors and assignment operators

    ~MyEC() { 
        if(!checked && parent == nullptr) {
            // log and or abort
        }
     }

    [[nodiscard]] std::error_code check() {
        checked = true;
        return ec;
     }
 
     void set(std::error_code err) {
         if(parent == nullptr)  ec = err;
         else parent->set(err);
     }
private:
    MyEC* parent = nullptr;
    checked = true;
    std::error_code ec {};
};

int foo(MyEC&& err) {
    err.set(/* some error */);
    return 5;
}

int foo1(MyEC&&) {
    return 4;
}

void bar() {
    MyEC err;
    foo(std::move(err));
    // err has the error code and if its not checked, we will know
 

    foo1(std::move(err));
    // even though no error occurs, we will abort if err is not checked.
}

It will even then abort, when the error code is not set but also not checked, which is pretty nice. It has a lot of uses after move, which is a bit weird, but this is no problem here.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM