Evgenii Legotckoi
Evgenii LegotckoiТам. 6, 2018, 2:52 Т.Ж.

RAII идиомасы және құрылымдық бағдарламалаудан алынған функцияның бір кіру нүктесі және бір шығу нүктесі болуы керек деген мәлімдеме

Мазмұны

Мир программирования на C++ в новых стандартах позволяет нам вытворять самые разные вещи, благодаря которым можно спокойно отказываться от некоторых старых утверждений или принципов, либо просто гибко подходить к этим принципам.

Хотелось бы изложить свой взгляд на работу идиомы RAII и стандарта C++11 относительно одно устоявшийся принцип авторство которого приписывается Эдсгеру Дейкстре :

«модуль (в данном случае функция) должен иметь только одну точку входа и только одну точку выхода»

Для данного принципа множественные return в функции/методе противоречат принципам структурного программирования по следующим причинам:

  • сложность отладки кода с применением множественных возвратов return увеличивается с количеством этих самых return, то есть никогда не знаешь, когда именно произошёл выход из функции или метода объекта.
  • сложность поддержки кода, когда при первоначальном взгляде на функцию не заметны все точки вызода. Также не известно, выполнится ли код добавленный в конец функции или нет, особенно, если по логике программы этот код должен выполняться всегда. То есть в случае с множествнными return придётся внедрять данный код перед каждым вызовом оператора return.

Но современный C++ уже значительно изменился со времён Дейкстры и средства последних стандартов C++ позволяют обойти или значительно нивелировать влияние причин, вызвавших формулирование принципов структурного программирования.


Одними из таких средств в C++ могут быть идиома RAII , лямбда функции , и std::function из стандартной библиотеки.

RAII (Resource Acquisition Is Initialization) Получение ресурса есть инициализация - программная идиома объектно-ориентированного программирования, смысл которой заключается в том, что с помощью тех или иных программных механизмов получение некоторого ресурса неразрывно совмещается с инициализацией, а освобождение - с уничтожением объекта.

Таким образом благодаря RAII и лямбда-функциям мы сможем обойти некоторые проблемы с множественными return, в частности с тем, что некоторые код должен всегда вызываться в конце функции, вне зависимости от остальной логики кода функции. Но об этом ближе к концу статьи.

А сейчас посмотрим на доводы за и против использования множественных return.

Первой причиной является то, что возрастает сложность отладки программного кода при наличии множественных return. Но в тоже время уже 2018й год и современные отладчики позволяют определить с помощью точек останова, откуда вышло выполнение функции, а наличие множественных return к тому же позволяет значительно уменьшить вложенность кода при использовании конструкций if else . Таким образом можем получить более компактный программный код, что только пойдёт на пользу пониманию того, что функция делает, несмотря на наличие множественных return.

Рассмотрим на примере

Есть две функции, которые вызываются в другой главной функции и по результату работы тех первых двух функции строится алгоритм работы главной функции.

bool exampleFunction_1();
bool exampleFunction_2();

Главное функция, написанная по принципам структурного программирования

int examlpeFunctionMain()
{
    int result = 0;

    if (exampleFunction_1())
    {
        result = 1;
    }
    else if (exampleFunction_2())
    {
        result = 2;
    }

    return result;
}

Если бы таких функций было бы больше то могла бы значительно  возрасти вложенность конструкций if else, что на пользу программному коду не пошло бы.

Поэтому перепишем данный код на использование множественных return.

int examlpeFunctionMain()
{
    if (exampleFunction_1()) return 1;
    if (exampleFunction_2()) return 2;
    return 0;
}

Код стал значительно компактнее и очевидно, что гораздо понятнее. То есть использование нескольких точек выхода из функции может позволить наоборот улучшить код, а не усложнить его.

А теперь приведём аргументы о том, что одна точка выхода будет лучше в том случае, если потребуется, чтобы определённый программный код в конце функции всегда вызывался, а если использовать несколько точек выхода, то потребуется дублировать этот код и добавлять его перед всеми точками выхода. Например, таким кодом может быть логирование результата работы функции.

Рассмотрим пример такого аргумента.

int examlpeFunctionMain()
{
    int result = 0;

    if (exampleFunction_1())
    {
        result = 1;
    }
    else if (exampleFunction_2())
    {
        result = 2;
    }

    std::cout << "Logging result " << result << std::endl;
    return result;
}

В конце представленной выше функции имеется некоторый код, эмулирующий логирование. Тогда при наличии нескольких точек останова этот код придётся дублировать, и наш предыдущий красивый вариант станет вот таким некрасивым.

int examlpeFunctionMain()
{
    if (exampleFunction_1())
    {
        std::cout << "Logging result " << 1 << std::endl;
        return 1;
    }

    if (exampleFunction_2())
    {
        std::cout << "Logging result " << 2 << std::endl;
        return 2;
    }

    std::cout << "Logging result " << 0 << std::endl;
    return 0;
}

Как видите здесь мало того, что количество строк возросло на одну, так ещё появилась возможность допустить ошибку при выполнении копирования, если забыть поменять цифру в выводе логирования.

Подобный аргумент за наличие лишь одной точки выхода из функции становится вполне разумным.

Но предлагаю теперь обратиться к современным возможностям языка программирования C++.

Идиом RAII подразумевает, что при уничтожении объекта в деструкторе можно освободить память, а также выполнить какой-нибудь программный код. Таким кодом может быть выполнение код логирования. Но у нас нет никаких подобных объектов? Да, на данный момент нет, но предлагаю написать класс для использования такого объекта.

Это будет шаблонный класс ScopExit. Рассмотрим его ниже.

#ifndef SCOPEEXIT_H
#define SCOPEEXIT_H

#include <functional>

class ScopeExit
{
public:
    template<typename T>
    explicit inline ScopeExit(T&& onScopeExitFunction) :
        m_onScopeExitFunction(std::forward<T>(onScopeExitFunction))
    {
    }

    inline ~ScopeExit()
    {
        m_onScopeExitFunction();
    }

private:
    std::function<void()> m_onScopeExitFunction;
};

#endif // SCOPEEXIT_H

Класс имеет приватное поле std::function, данное поле будет отвечать за хранение необходимого нам метода, который выполнит код в конце выполнения функции.

В конструкторе класса данное поле инициализируется переданным шаблонным аргументом, который может быть функтором или лямбда функцией.

В деструкторе класса данная функция вызывается. То есть когда объект будет уничтожаться, будет вызвана функция, которую мы поместим в объект данного класса при его создании.

С использованием данного класса можно будет переписать выше представленный код следующим образом:

int examlpeFunctionMain()
{
    int result = 0;
    ScopeExit scopeExit([&result](){ std::cout << "Logging result " << result << std::endl; });

    if (exampleFunction_1()) return (result = 1);
    if (exampleFunction_2()) return (result = 2);
    return 0;
}

Смысл данного код заключается в том, что имеется переменная result, которая будет хранить код, с которым завершится выполнение функции.

Далее создаётся объект класса для ScopeExit, который при завершении метода будет уничтожен и вызовет в деструкторе лямбду.

Данная лямбда передаётся в качестве аргумента в конструктор класса ScopeExit. При этом лямбда захватывает переменную result, чтобы получить актуальный код завершения функции.

Далее выполняются проверки и функция завершается в одной из трёх точек выхода, возвращая значение кода завершения функции. Что важно, лямбда функция выполнится вне зависимости от того, где завершилось выполнение функции. А значит и логирование гарантированно будет выполнено вне зависимости от того, забыли его прописать или нет.

Заключение

Данный пример искусственный и можно также забыть присвоить значение переменной result, но если нужно просто выполнить какой-то программный код вне зависимости от того, где произошло завершение функции, то данный вариант вполне подходит. А значит и утверждение о том, что некоторый код может быть не выполнен в конце функции также теряет под собой основание.

В общем придерживаться устоявшихся принципов разработки - это хорошо, но их придумали много лет назад, а средства разработки шагнули далеко вперёд уже. Поэтому нужно просто хорошо изучать свой язык программирования и думать головой. Поскольку многие проблемы разработки, которые были 20 лет назад, сейчас могут быть изящно решены средствами языка программирования.

Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.

Ол саған ұнайды ма? Әлеуметтік желілерде бөлісіңіз!

Пікірлер

Тек рұқсаты бар пайдаланушылар ғана пікір қалдыра алады.
Кіріңіз немесе Тіркеліңіз
m
  • molni99
  • Қаз. 26, 2024, 11:37 Т.Ж.

C++ - Тест 004. Указатели, Массивы и Циклы

  • Нәтиже:80ұпай,
  • Бағалау ұпайлары4
m
  • molni99
  • Қаз. 26, 2024, 11:29 Т.Ж.

C++ - Тест 004. Указатели, Массивы и Циклы

  • Нәтиже:20ұпай,
  • Бағалау ұпайлары-10

C++ - Тест 003. Условия и циклы

  • Нәтиже:42ұпай,
  • Бағалау ұпайлары-8
Соңғы пікірлер
i
innorwallҚар. 8, 2024, 7:51 Т.Қ.
Джанго - Сабақ 036. Әлеуметтік желілер арқылы аутентификацияны қалай қосуға болады. Байланыста buy priligy online usa In addition, it might not be effective at the doses recommended, because of your previous tolerance to a similar type of drug
i
innorwallҚар. 8, 2024, 6:40 Т.Қ.
Qt/C++ - 039-сабақ. QSqlTableModel жүйесінде жолды бағандағы мән бойынша бояу әдісі priligy results This slowing of eGFR decline was observed in patients with and without low eGFR and in those with and without type 2 diabetes
i
innorwallҚар. 8, 2024, 1:45 Т.Қ.
QML - Урок 002. QML Android жүйесіндегі пайдаланушы түймесі 2007; 14 2 270 83 priligy dapoxetine 60mg Testicular imaging is sort of a unique niche right now, Гў
i
innorwallҚар. 8, 2024, 12:32 Т.Қ.
C++ - #pragma бір рет құрастыруды жылдамдатады ма? It could cause harm to the unborn baby buy generic priligy
Енді форумда талқылаңыз
i
innorwallҚар. 8, 2024, 6:08 Т.Қ.
добавить qlineseries в функции School of Nursing, Long Island University, Brooklyn Campus, Brooklyn, NY, USA priligy dapoxetine 30mg
9
9AnonimҚаз. 25, 2024, 7:10 Т.Қ.
Машина тьюринга // Начальное состояние 0 0, ,<,1 // Переход в состояние 1 при пустом символе 0,0,>,0 // Остаемся в состоянии 0, двигаясь вправо при встрече 0 0,1,>…
F
FynjyШілде 22, 2024, 2:15 Т.Қ.
при создании qml проекта Kits есть но недоступны для выбора Поставил Qt Creator 11.0.2. Qt 6.4.3 При создании проекта Qml не могу выбрать Kits, они все недоступны, хотя настроены и при создании обычного Qt Widget приложения их можно выбрать. В чем может …

Бізді әлеуметтік желілерде бақылаңыз