Evgenii Legotckoi
Evgenii Legotckoi6. August 2018 02:52

Das Idiom RAII und das Prinzip der strukturierten Programmierung, dass eine Funktion einen Einstiegs- und einen Ausstiegspunkt haben muss

Inhalt

Die Welt der C++-Programmierung in den neuen Standards erlaubt uns, alle möglichen Dinge zu tun, dank denen wir einige alte Anweisungen oder Prinzipien sicher aufgeben oder einfach flexibel in der Annäherung an diese Prinzipien sein können.

Ich möchte meine Meinung zur Arbeit des RAII-Idioms und des C++11-Standards in Bezug auf ein etabliertes Prinzip zum Ausdruck bringen, dessen Urheberschaft Edsger Dijkstra zugeschrieben wird:

> „ein Modul (in diesem Fall eine Funktion) darf nur einen Eintrittspunkt und nur einen Austrittspunkt haben“
>
>

Für dieses Prinzip verstoßen mehrere Rückgaben in einer Funktion/Methode aus folgenden Gründen gegen die Prinzipien der strukturierten Programmierung:

  • Die Komplexität des Debugging-Codes mit mehreren Rückgaben steigt mit der Anzahl dieser gleichen Rückgaben, dh Sie wissen nie genau, wann die Funktion oder Methode des Objekts beendet wurde.
  • die Komplexität der Codeunterstützung, wenn auf den ersten Blick auf die Funktion nicht alle Aufrufpunkte sichtbar sind. Es ist auch nicht bekannt, ob der am Ende der Funktion angefügte Code ausgeführt wird oder nicht, insbesondere wenn dieser Code laut Programmlogik immer ausgeführt werden soll. Das heißt, bei mehreren Rückgaben müssen Sie diesen Code vor jedem Aufruf der return-Anweisung implementieren.

Aber das moderne C++ hat sich seit der Zeit von Dijkstra bereits erheblich verändert, und die Werkzeuge der neuesten C++-Standards ermöglichen es, den Einfluss der Gründe, die zur Formulierung der Prinzipien der strukturierten Programmierung geführt haben, zu umgehen oder erheblich einzuebnen.


Einige dieser Einrichtungen in C++ sind das Idiom RAII , lambda functions und std::function aus der Standardbibliothek.

> RAII (Resource Acquisition Is Initialization) Der Erwerb einer Ressource ist Initialisierung - eine Softwaresprache der objektorientierten Programmierung, deren Bedeutung darin besteht, dass unter Verwendung bestimmter Softwaremechanismen der Erwerb einer Ressource untrennbar mit der Initialisierung verbunden ist, und befreien - mit Zerstörung des Objekts.
>
>

So können wir dank RAII- und Lambda-Funktionen einige der Probleme mit mehrfachen Rückgaben umgehen, insbesondere die Tatsache, dass am Ende der Funktion immer irgendein Code aufgerufen werden muss, unabhängig von der restlichen Logik des Funktionscodes . Aber dazu mehr am Ende des Artikels.

Sehen wir uns nun die Vor- und Nachteile der Verwendung mehrerer Returns an.

Der erste Grund ist, dass die Komplexität des Debuggens des Programmcodes bei Vorhandensein mehrerer Rückgaben zunimmt. Aber gleichzeitig ist es bereits 2018 und moderne Debugger ermöglichen es Ihnen, mithilfe von Breakpoints festzustellen, woher die Funktionsausführung kam, und das Vorhandensein mehrerer Rückgaben ermöglicht es Ihnen auch, die Codeverschachtelung bei der Verwendung von [if else]-Strukturen (https ://evileg.com/post/256/). Auf diese Weise können wir einen kompakteren Programmcode erhalten, der trotz des Vorhandenseins mehrerer Rückgaben nur dem Verständnis der Funktion zugute kommt.

Schauen wir uns ein Beispiel an

Es gibt zwei Funktionen, die in einer anderen Hauptfunktion aufgerufen werden, und basierend auf dem Ergebnis der Arbeit dieser ersten beiden Funktionen wird ein Algorithmus für die Arbeit der Hauptfunktion erstellt.

bool exampleFunction_1();
bool exampleFunction_2();

Hauptfunktion nach den Prinzipien der strukturierten Programmierung geschrieben

int examlpeFunctionMain()
{
    int result = 0;

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

    return result;
}

Gäbe es mehr solcher Funktionen, dann könnte die Verschachtelung von if-else-Konstrukten deutlich zunehmen, was dem Programmcode nicht zugute käme.

Daher schreiben wir diesen Code neu, um mehrere Rückgaben zu verwenden.

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

Der Code ist viel kompakter und offensichtlich viel übersichtlicher geworden. Das heißt, die Verwendung mehrerer Austrittspunkte aus einer Funktion kann im Gegenteil den Code verbessern, anstatt ihn zu verkomplizieren.

Lassen Sie uns nun argumentieren, dass ein einzelner Exit-Punkt besser ist, wenn Sie möchten, dass bestimmter Programmcode am Ende einer Funktion immer aufgerufen wird, und wenn Sie mehrere Exit-Punkte verwenden, müssen Sie diesen Code duplizieren und vor allen Exits hinzufügen Punkte. Beispielsweise kann ein solcher Code das Ergebnis einer Funktion protokollieren.

Betrachten Sie ein Beispiel für ein solches 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;
}

Am Ende der obigen Funktion befindet sich ein Code, der die Protokollierung emuliert. Wenn es dann mehrere Haltepunkte gibt, muss dieser Code dupliziert werden, und unsere vorherige schöne Version wird so hässlich.

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

Wie Sie hier sehen können, hat sich nicht nur die Anzahl der Zeilen um eins erhöht, sondern es ist auch möglich geworden, beim Kopieren einen Fehler zu machen, wenn Sie vergessen haben, die Nummer in der Protokollausgabe zu ändern.

Ein solches Argument dafür, nur einen Austrittspunkt von einer Funktion zu haben, wird ziemlich vernünftig.

Aber jetzt schlage ich vor, mich den modernen Funktionen der Programmiersprache C ++ zuzuwenden.

Das RAII-Idiom impliziert, dass, wenn ein Objekt im Destruktor zerstört wird, sowohl Speicher freigegeben als auch Programmcode ausgeführt werden kann. Ein solcher Code kann die Ausführung des Protokollierungscodes sein. Aber wir haben keine ähnlichen Objekte? Ja, im Moment nicht, aber ich schlage vor, eine Klasse zu schreiben, um ein solches Objekt zu verwenden.

Dies ist die ScopExit-Vorlagenklasse. Betrachten wir es unten.

#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

Die Klasse hat ein privates std::function-Feld, dieses Feld ist für das Speichern der benötigten Methode verantwortlich, die den Code am Ende der Funktionsausführung ausführt.

Im Klassenkonstruktor wird dieses Feld mit dem übergebenen Template-Argument initialisiert, das ein Funktor oder eine Lambda-Funktion sein kann.

Diese Funktion wird im Klassendestruktor aufgerufen. Das heißt, wenn das Objekt zerstört wird, wird die Funktion aufgerufen, die wir in das Objekt dieser Klasse einfügen, wenn es erstellt wird.

Mit dieser Klasse ist es möglich, den obigen Code wie folgt umzuschreiben:

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

Die Bedeutung dieses Codes ist, dass es eine Ergebnisvariable gibt, die den Code speichert, mit dem die Funktion endet.

Als nächstes wird ein Klassenobjekt für ScopeExit erstellt, das zerstört wird, wenn die Methode endet, und das Lambda im Destruktor aufruft.

Dieses Lambda wird als Argument an den Konstruktor der ScopeExit-Klasse übergeben. Dabei erfasst das Lambda die Ergebnisvariable, um den tatsächlichen Exit-Code der Funktion zu erhalten.

Weitere Überprüfungen werden durchgeführt und die Funktion endet an einem der drei Exit-Punkte und gibt den Wert des Funktions-Exit-Codes zurück. Wichtig ist, dass die Lambda-Funktion unabhängig davon ausgeführt wird, wo die Funktion endete. Das bedeutet, dass die Protokollierung garantiert durchgeführt wird, unabhängig davon, ob sie die Registrierung vergessen haben oder nicht.

Fazit

Dieses Beispiel ist künstlich und Sie können auch vergessen, der Variablen result einen Wert zuzuweisen, aber wenn Sie nur einen Programmcode ausführen möchten, unabhängig davon, wo die Funktion endet, dann ist diese Option in Ordnung. Damit verliert auch die Aussage, dass irgendein Code am Ende der Funktion nicht ausgeführt werden darf, ihre Grundlage.

Im Allgemeinen ist es gut, sich an etablierte Entwicklungsprinzipien zu halten, aber sie wurden vor vielen Jahren erfunden, und Entwicklungstools sind bereits weit voraus. Daher müssen Sie nur Ihre Programmiersprache gut lernen und mit dem Kopf denken. Denn viele Entwicklungsprobleme, die es vor 20 Jahren noch gab, lassen sich heute elegant mit einer Programmiersprache lösen.

Рекомендуємо хостинг TIMEWEB
Рекомендуємо хостинг TIMEWEB
Stabiles Hosting des sozialen Netzwerks EVILEG. Wir empfehlen VDS-Hosting für Django-Projekte.

Magst du es? In sozialen Netzwerken teilen!

Kommentare

Nur autorisierte Benutzer können Kommentare posten.
Bitte Anmelden oder Registrieren
Letzte Kommentare
ИМ
Игорь Максимов5. Oktober 2024 07:51
Django – Lektion 064. So schreiben Sie eine Python-Markdown-Erweiterung Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas55. Juli 2024 11:02
QML - Lektion 016. SQLite-Datenbank und das Arbeiten damit in QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
k
kmssr8. Februar 2024 18:43
Qt Linux - Lektion 001. Autorun Qt-Anwendung unter Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
Qt WinAPI - Lektion 007. Arbeiten mit ICMP-Ping in Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
EVA
EVA25. Dezember 2023 10:30
Boost - statisches Verknüpfen im CMake-Projekt unter Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
Jetzt im Forum diskutieren
J
JacobFib17. Oktober 2024 03:27
добавить qlineseries в функции Пользователь может получить любые разъяснения по интересующим вопросам, касающимся обработки его персональных данных, обратившись к Оператору с помощью электронной почты https://topdecorpro.ru…
JW
Jhon Wick1. Oktober 2024 15:52
Indian Food Restaurant In Columbus OH| Layla’s Kitchen Indian Restaurant If you're looking for a truly authentic https://www.laylaskitchenrestaurantohio.com/ , Layla’s Kitchen Indian Restaurant is your go-to destination. Located at 6152 Cleveland Ave, Colu…
КГ
Кирилл Гусарев27. September 2024 09:09
Не запускается программа на Qt: точка входа в процедуру не найдена в библиотеке DLL Написал программу на C++ Qt в Qt Creator, сбилдил Release с помощью MinGW 64-bit, бинарнику напихал dll-ки с помощью windeployqt.exe. При попытке запуска моей сбилженной программы выдаёт три оши…
F
Fynjy22. Juli 2024 04:15
при создании qml проекта Kits есть но недоступны для выбора Поставил Qt Creator 11.0.2. Qt 6.4.3 При создании проекта Qml не могу выбрать Kits, они все недоступны, хотя настроены и при создании обычного Qt Widget приложения их можно выбрать. В чем может …

Folgen Sie uns in sozialen Netzwerken