Правильно завершить разветвленный процесс в C++

Читая ответ на Как закончить код C++, я узнал, что вызов exit из C++ код плохой. Но что, если я разветвил дочерний процесс, который должен где-то завершиться и настолько глубоко в стеке вызовов, что передача его кода выхода в main была бы невозможна?

Я нашел несколько альтернатив, как это сделать - по общему признанию, это стало немного длинным, но потерпите меня:

#include <sys/stat.h>
#include <unistd.h>
#include <sys/wait.h>

#include <cstdlib>

#include <memory>
#include <string>
#include <iostream>

thread_local char const* thread_id{"main"};

struct DtorTest {
    std::string where{};
     DtorTest(void) = default;
     DtorTest(std::string _where) : where{std::move(_where)} {}
    ~DtorTest(void) {
        std::cerr << __func__ << " in " << thread_id << ", origin " << where << std::endl;
    }
};

std::unique_ptr< DtorTest > freeatexit{nullptr};

pid_t
fork_this(pid_t (*fork_option)(void))
{
    DtorTest test(__func__);
    return fork_option();
}

pid_t
fork_option0(void)
{
    pid_t pid;
    if ((pid = fork()))
        return pid;
    thread_id = "child";
    DtorTest test(__func__);
    std::exit(EXIT_SUCCESS);
}

pid_t
fork_option1(void)
{
    pid_t pid;
    if ((pid = fork()))
        return pid;
    thread_id = "child";
    DtorTest test(__func__);
    std::_Exit(EXIT_SUCCESS);
}

pid_t
fork_option2(void)
{
    pid_t pid;
    if ((pid = fork()))
        return pid;
    {
    thread_id = "child";
    DtorTest test(__func__);
    }
    std::_Exit(EXIT_SUCCESS);
}

pid_t
fork_option3(void)
{
    pid_t pid;
    if ((pid = fork()))
        return pid;
    thread_id = "child";
    DtorTest test(__func__);
    throw std::exception();
}

int main(int argc, char const *argv[])
{
    int status;
    const int option = (argc > 1) ? std::stoi(argv[1]) : 0;
    pid_t pid;
    freeatexit = std::unique_ptr< DtorTest >(new DtorTest(__func__));


    switch (option) {
      case 0:
        pid = fork_this(fork_option0);
        break;
      case 1:
        pid = fork_this(fork_option1);
        break;
      case 2:
        pid = fork_this(fork_option2);
        break;
      case 3:
        try {
            pid = fork_this(fork_option3);
        } catch (std::exception) {
            return EXIT_SUCCESS;
        }
        break;
      case 4:
        try {
            pid = fork_this(fork_option3);
        } catch (std::exception) {
            std::_Exit(EXIT_SUCCESS);
        }
        break;
      default:
        return EXIT_FAILURE;
    }

    waitpid(pid, &status, 0);

    return status;
}

Вариант 0

Возможно худшее:

./a.out 0
~DtorTest in main, origin fork_this
~DtorTest in child, origin main
~DtorTest in main, origin main

Проблема в том, что деструктор test в fork_option0 не вызывается, потому что std::exit просто игнорирует любой объект с автоматическим хранением. Хуже того, unique_ptr деструктор вызывается дважды.

Опция 1

./a.out 1
~DtorTest in main, origin fork_this
~DtorTest in main, origin main

Та же проблема с деструктором в fork_option1 потому что std::_Exit также игнорирует автоматическое хранение. По крайней мере unique_ptr деструктор вызывается только один раз.

Вариант 2

Кажется, это работает, деструкторы называются правильно.

./a.out 2
~DtorTest in main, origin fork_this
~DtorTest in child, origin fork_option2
~DtorTest in main, origin main

Вариант 3

Это ближайшее приближение к возврату из основного совета, однако у него есть несколько проблем:

./a.out 3
~DtorTest in main, origin fork_this
~DtorTest in child, origin fork_option3
~DtorTest in child, origin fork_this
~DtorTest in child, origin main
~DtorTest in main, origin main

Хотя деструкторы в fork_option3 Правильно вызваны два двойных освобождения. Сначала unique_ptr, второй объект в fork_this,

Вариант 4

./a.out 4
~DtorTest in main, origin fork_this
~DtorTest in child, origin fork_option3
~DtorTest in child, origin fork_this
~DtorTest in main, origin main

Чуть лучше, чем третий вариант, потому что двойной свободный от unique_ptr ушел Однако объекты в fork_this все еще дважды free'd.

Итак, как правильно выйти / завершить дочерний процесс?

Из приведенных выше экспериментов может показаться, что option2 работает лучше всего. Тем не менее, я мог пропустить другие проблемы с std::_Exit (см. Как завершить код C++)

1 ответ

Это традиционная схема fork,

#include <sys/stat.h>
#include <unistd.h>
#include <sys/wait.h>
#include <cstdlib>

#include <iostream>
#include <fstream>

struct DtorTest {
    ~DtorTest(void) { std::cout << "d'tor never runs\n"; }
};

int
child(void)
{
    // only child
    DtorTest dtortest;  // D'tor never runs
    std::ofstream fout("inchild.txt");  // file not flushed
    fout << "this is in the child\n";
    return 0;
}

int
main(void)
{
    pid_t pid;
    if ((pid = fork()))
      int status;
      waitpid(pid, &status, 0);
      return status;
   } else {
      return child();
   }
}

Не использовать extern "C" для системы включайте файлы. Если вам это нужно, то вы должны использовать древний компилятор, и все ставки сняты.

Другие вопросы по тегам