The idiom RAII and the principle of structured programming that a function must have one entry point and one exit point

template, lambda, C++11

The world of programming in C ++ in the new standards allows us to get up a variety of things, thanks to which we can safely abandon some old statements or principles, or simply flexibly approach these principles.

I would like to outline my view on the work of the RAII idiom and the C ++ 11 standard with respect to one established principle whose authorship is attributed to Edsger Dijkstra :

"The module (in this case the function) must have only one entry point and only one exit point"

For this principle, multiple returns in a function / method are contrary to the principles of structured programming for the following reasons:

  • the complexity of debugging code with the use of multiple returns returns increases with the number of these same return, that is, you never know when the function or method of an object just exited.
    the complexity of code support, when all the points of the call are not visible at the initial look at the function. Also, it is not known whether the code added to the end of the function will execute or not, especially if the program logic should always execute this code. That is, in the case of multiple returns, you will have to implement this code before every call to the return operator.

But modern C ++ has already changed significantly since the times of Dijkstra and the means of the latest C ++ standards allow to bypass or significantly level out the influence of the reasons that caused the formulation of the principles of structured programming.

One of these tools in C++ can be the RAII idiom, lambda function , and std::function from the standard library.

RAII (Resource Acquisition Is Initialization) - the program idiom of object-oriented programming, the meaning of which is that with the help of certain software mechanisms the obtaining of a certain resource is inseparably combined with initialization, and the release is with the destruction of the object.

Thus, thanks to RAII and lambda functions, we can get around some problems with multiple return, in particular with the fact that some code should always be called at the end of the function, regardless of the rest of the function code logic. But this is closer to the end of the article.

And now let's look at the pros and cons of using multiple return.

The first reason is that the complexity of debugging the program code increases with multiple return. But at the same time, already in 2018, modern debuggers allow you to define using breakpoints where the function came from, and the presence of multiple returns also allows you to significantly reduce the nesting of the code when using the if else constructs. Thus, we can get a more compact program code, which only will benefit from an understanding of what the function does, despite the presence of multiple return.

Consider the example

There are two functions that are called in another main function and the algorithm of the main function is constructed from the result of the work of those first two functions.

bool exampleFunction_1();
bool exampleFunction_2();

The main function, written according to the principles of structured programming

int examlpeFunctionMain()
{
    int result = 0;

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

    return result;
}

If there were more such functions then the nesting of the if else constructs could increase significantly, which would not benefit the program code.

Therefore, we rewrite this code to use multiple return.

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

The code has become much more compact and obvious, which is much clearer. That is, the use of multiple exit points from a function may allow the code to be improved on the contrary, rather than complicate it.

And now we give arguments that one exit point will be better if it is required that a certain program code at the end of the function is always called, and if you use several exit points, you need to duplicate this code and add it before all exit points. For example, this code can be the logging of the result of the function.

Let us consider an example of such an argument.

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;
}

At the end of the above function, there is some code that emulates the logging. Then, if there are several breakpoints, this code will have to be duplicated, and our previous beautiful variant will become such ugly.

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;
}

As you can see here, not only is the number of rows increased by one, so there was also the possibility of making a mistake when copying, if you forget to change the digit in the output of the log.

Such an argument for the presence of only one exit point from the function becomes quite reasonable.

But now I suggest that we turn to the modern possibilities of the C ++ programming language.

The idiom RAII implies that when you destroy an object in the destructor, you can free up memory, and also execute some program code. Such code can be the execution of a logging code. But we do not have any such objects? Yes, at the moment, no, but I suggest writing a class for using such an object.

This will be a sample ScopExit class. Let us consider it below.

#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

The class has a private field std::function , this field will be responsible for storing the method we need, which will execute the code at the end of the function.

In the class constructor, this field is initialized by the passed template argument, which can be a functor or a lambda function.

In the class destructor, this function is called. That is, when the object is destroyed, a function will be called, which we place in the object of this class when it is created.

Using this class, you can rewrite the above code as follows:

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;
}

The meaning of this code is that there is a variable result, which will store the code with which the function will end.

Next, a class object is created for ScopeExit, which at the end of the method will be destroyed and will cause the lambda in the destructor.

This lambda is passed as an argument to the constructor of the ScopeExit class. In this case, the lambda grabs the variable result to get the actual function completion code.

Further checks are performed and the function ends at one of the three exit points, returning the value of the function completion code. What is important, the lambda function will be executed regardless of where the function ended. This means that the logging is guaranteed to be fulfilled, regardless of whether they forgot to register it or not.

Conclusion

This example is artificial and you can also forget to assign the value of the variable result, but if you just need to execute some program code, regardless of where the function ended, then this option is quite suitable. So, the statement that some code may not be executed at the end of the function also loses its foundation.

In general, adhere to the established principles of development - it's good, but they came up with it many years ago, and the development tools stepped forward already. Therefore, you just need to study your programming language well and think with your head. Since many development problems that were 20 years ago can now be elegantly solved using the programming language.

We recommend hosting TIMEWEB
We recommend hosting TIMEWEB
Stable hosting, on which the social network EVILEG is located. For projects on Django we recommend VDS hosting.
Support the author Donate

Comments

Only authorized users can post comments.
Please, Log in or Sign up
Looking for a Job?
25,000.00 руб. - 30,000.00 руб.
Разработчик Qt/C++
Barnaul, Altai Krai, Russia

For registered users on the site there is a minimum amount of advertising

JuA
Sept. 17, 2019, 7:51 a.m.
Julija Aleksandrova

C++ - Test 001. The first program and data types

  • Result:33points,
  • Rating points-10
JuA
Sept. 17, 2019, 7:36 a.m.
Julija Aleksandrova

C ++ - Test 004. Pointers, Arrays and Loops

  • Result:10points,
  • Rating points-10
VD
Sept. 16, 2019, 10:47 a.m.
Viktor Dzen'kiv

C++ - Test 002. Constants

  • Result:75points,
  • Rating points2
Last comments
Sept. 17, 2019, 5:07 a.m.
Misha Lebedev

Кстати интересные темы нашёл тут https://emacsway.github.io/ru/django-framework/#django-models Может что полезного тоже Евгений найдёте
Sept. 17, 2019, 3:50 a.m.
Misha Lebedev

Доброго времени суток. Спасибо за хороший ответ, У меня ситуация така что в галлереи будет несколько миллионов фотографий с фильтрами и тегами , и я опасаюсь за производительност . Это ос…
Sept. 17, 2019, 2:23 a.m.
Evgenij Legotskoj

Добрый день. Да, я тоже читал ту статью в своё время и согласен с тем, что внешние ключи гораздо лучше, чем GenericForeignKey. Выборки в ряде случае работают быстрее. Но лично мне про…
Sept. 14, 2019, 4:08 p.m.
Misha Lebedev

Приветствую вас Евгений , давно наблюда за развитием вашего замечательного портала, много полезно тут нашел , переодически зачитываюсь. Теперь по сушеству, делаю портал и там идеально ложи…
Sept. 10, 2019, 3:38 p.m.
Evgenij Legotskoj

function view для модели Article и LikeDislike.LIKE будет выглядеть так def like(request, pk): obj = Article.objects.get(pk=pk) try: likedislike = LikeDislike.objects.get(cont…
Now discuss on the forum
p
Sept. 17, 2019, 4:02 a.m.
pstMem

Да, действительно нужно дебажить, по другому не словить исключение. Уже решил проблему, был выход за предел массива, не правильные входные данные, так что всегда проверяйте размер массива.
Sept. 17, 2019, 2:39 a.m.
Evgenij Legotskoj

Добрый день! На удалённом сервере вряд ли. Этот класс из core модуля, а удалённый сервер - это ещё и network модуль нужно подтягивать. Тут на удалэнном сервере нужно делать программу…
Sept. 17, 2019, 2:30 a.m.
Evgenij Legotskoj

Добрый день! Попробуйте toHex() А также создние QString с помощью from методов. Может быть QString::fromLatin1(). В документации на QString почти два десятка методов from, один из них…
m
Sept. 16, 2019, 12:54 p.m.
mihamuz

Однозначно PostgreSql не ниже 10 ки.
R
Sept. 16, 2019, 6:09 a.m.
RED_Spider

прочитайте https://doc.qt.io/archives/qt-5.11/osx-deployment.html QMAKE_POST_LINK += "~/Qt/5.12.0/clang_64/bin/macdeployqt $${TARGET}.app $$escape_expand( \\n\\t )"
EVILEG
About
Services
© EVILEG 2015-2019
Recommend hosting TIMEWEB