(C/C++) From error to exception (error->exception)

foreword

Exceptions are a very important mechanism in modern mainstream object-oriented programming languages. The C language with a long history does not have such a mechanism.

Most people know how exceptions are used, but they haven't thought about why this mechanism occurs.

This article will briefly introduce and analyze from the errors of the C language era to the exceptions of the C++ era.

References: What is "abnormal"? When to use it? How to use? Many people don't understand, in fact, it's just one sentence. Learn this, benefit for life!

In fact, this article is a note

evolution

error neutrality

error neutral

Subfunctions of this function are allowed to generate errors during operation.

Example scenario :

Read the information of a specified text.

Analysis :

In case of an error, the console will print an error message.

In order not to let the wrong state continue to run, it is necessary to constantly judge the return value of the function.

reason:

Macro: errnoExpands into a static variable or function independent of each thread. Operations that are run will write the cause of the error into this macro.

#include <error.h>
#include <stdio.h>
#include <stdlib.h>

char *read_file(const char *path) {
    
    
    FILE *fp = fopen(path, "r");
    if (!fp) {
    
    
        perror("open fail\n");
        return NULL;
    }

    size_t cap = 1023;
    // +1 ('\0')的预留位置
    char *str = malloc(cap + 1);
    if (!str) {
    
    
        fclose(fp);

        perror("malloc fail\n");
        return NULL;
    }

    cap = fread(str, sizeof(*str), cap, fp);
    // 读取0个,且是流错误
    if (!cap && ferror(fp)) {
    
    
        free(str);
        fclose(fp);

        perror("fread ferror fail\n");
        return NULL;
    }

    str[cap] = '\0';
    fclose(fp);
    return str;
}

int main(void) {
    
    
    char *str = read_file("h1ello.txt");
    if (!str) {
    
    
        perror("read fail\n");
        return EXIT_FAILURE;
    }

    printf("data:\n--------\n%s", str);
    free(str);
    return EXIT_SUCCESS;
}

exception neutrality

unusually neutral

Subfunctions called by this function are allowed to throw exceptions by any means. That is, exceptionally transparent.

Example scenario :

Read the information of a specified text.

Analysis :

With exceptions, various branch judgments are eliminated.

However, it does not deal with the abnormal state caused by the specific exception, but only exposes it to the place where the exception is caused and throws it to the caller.

#include <cstdio>
#include <fstream>
#include <memory>

::std::unique_ptr<char[]> read_file(const char* path) {
    
    
    ::std::ifstream ifs;
    // 设置异常状态为位
    ifs.exceptions(::std::ios::failbit);
    ifs.open(path);

    ::size_t cap = 1023;
    ::std::unique_ptr<char[]> str(new char[cap + 1]);
    ifs.get(str.get(), cap + 1, 0);
    return str;
}

int main(void) {
    
    
    try {
    
    
        auto str = read_file("hello.txt");
        ::std::printf("data:\n--------\n%s", str.get());
        return EXIT_SUCCESS;
    } catch (const std::exception& e) {
    
    
        ::std::printf("error: %s\n", e.what());
        return EXIT_FAILURE;
    }
}

exception safety

exception safe

Example scenario :

Add data to a set of storage objects.

Analysis :

Combine actions that are less aggressive.

Strong exception safety is guaranteed through copy and swap operations.

Although the overhead is high to a certain extent, the code is concise and safe.

Three levels :

  1. no-throw guarantee (no exception will be thrown)
  2. basic exception safety guarantee (no resource leaks)
  3. strong exception safety guarantee (strong exception safety guarantee)
#include <map>
#include <memory>

struct User {
    
    
    uint64_t uid;
    std::string name;
    int age;
};

std::map<uint64_t, std::shared_ptr<User>> users_by_uid;
std::multimap<std::string, std::shared_ptr<User>> users_by_name;
std::multimap<int, std::shared_ptr<User>> users_by_age;

// 事务机制。要么全部更新,要么什么都不更新 (保证外部调用的原子性)
// (强异常安全) copy and swap
void add_user(...) {
    
    
    auto user = std::make_shared<User>();
    // copy
    auto tmp_by_uid = users_by_uid;
    auto tmp_by_name = users_by_name;
    auto tmp_by_age = users_by_age;

    // operator may exception
    // basic exception safety guarantee
    tmp_by_uid.insert({
    
    user->uid, user});
    tmp_by_name.insert({
    
    user->name, user});
    tmp_by_age.insert({
    
    user->age, user});

    // swap (no-throw guarantee)
    tmp_by_uid.swap(users_by_uid);
    tmp_by_name.swap(users_by_name);
    tmp_by_age.swap(users_by_age);
}

Summary and reflection

The emergence of exceptions makes the code concise and safe.

When programming, you should keep the idea of ​​​​error/exception neutral.

Using exceptions can be thrown to the caller and let the caller handle this abnormal error. This is also one of the reasons for the abnormal mechanism.

When do we need to handle exceptions? Only when the exception occurs, the exception can be handled, and the exception is handled.

for example:

If vector memory expansion fails, it will be processed in place to prevent memory leaks.

A website request times out, and the specific sub-function of the operation cannot handle it, so an exception is thrown to let the superior caller judge the processing.




END

Guess you like

Origin blog.csdn.net/CUBE_lotus/article/details/131015612