In C ++ gibt es einige Funktionen, die als potenziell gefährlich eingestuft werden können. Bei Fehlkalkulationen im Design oder ungenauer Codierung können sie leicht zu Fehlern führen. Der Artikel enthält eine Auswahl solcher Funktionen sowie Tipps zur Reduzierung ihrer negativen Auswirkungen.
Inhaltsverzeichnis
Praemonitus, Praemunitus.
Vorgewarnt bedeutet bewaffnet. (lat.)
Einführung
In C ++ gibt es einige Funktionen, die als potenziell gefährlich eingestuft werden können. Bei Fehlkalkulationen im Design oder ungenauer Codierung können sie leicht zu Fehlern führen. Einige von ihnen können auf eine schwierige Kindheit zurückgeführt werden, andere auf den veralteten C ++ 98-Standard, andere sind bereits mit den Funktionen des modernen C ++ verbunden. Betrachten Sie die wichtigsten und versuchen Sie, Ratschläge zu geben, wie Sie ihre negativen Auswirkungen verringern können.
1. Typen
1.1. Bedingte Anweisungen und Bediener
Die Notwendigkeit der Kompatibilität mit C führt dazu, dass Sie in der Anweisung if(...)
und dergleichen jeden numerischen Ausdruck oder Zeiger ersetzen können und nicht nur Ausdrücke wie bool
. Das Problem wird durch die implizite Konvertierung von bool
nach int
in arithmetischen Ausdrücken und die Priorität einiger Operatoren verschärft. Dies führt beispielsweise zu folgenden Fehlern:
if(a=b)
wenn richtig if(a==b)
,
if(a<x<b)
, wenn richtig if(a<x && x<b)
,
if(a&x==0)
, wenn richtig if((a&x)==0)
,
if(Foo)
wenn richtig if(Foo())
,
if(arr)
wenn richtig if(arr[0])
,
if(strcmp(s,r))
wenn korrekt if(strcmp(s,r)==0)
.
Einige dieser Fehler verursachen eine Compiler-Warnung, jedoch keinen Fehler. Manchmal können auch Code-Analysatoren helfen. In C # sind solche Fehler fast unmöglich. if(...)
und ähnliche Anweisungen einen bool
Typ erfordern, können Sie bool
und numerische Typen nicht in arithmetischen Ausdrücken mischen.
Wie man kämpft:
- Programm ohne Warnungen. Leider hilft dies nicht immer, da einige der oben beschriebenen Fehler keine Warnungen enthalten.
- Verwenden Sie statische Code-Analysatoren.
- Altmodische Empfangstechnik: Wenn Sie mit einer Konstanten vergleichen, setzen Sie sie links, zum Beispiel
if(MAX_PATH==x)
. Es sieht ziemlich Eigentumswohnung aus (und hat sogar seinen eigenen Namen - "Yoda Notation"), und hilft in einer kleinen Anzahl von Fällen in Betracht gezogen. - Verwenden Sie das
const
Qualifikationsmerkmal so weit wie möglich. Auch hier hilft es nicht immer. - Gewöhnen Sie sich daran, die richtigen logischen Ausdrücke zu schreiben:
if(x!=0)
anstelle von if(x)
. (Obwohl Sie hier in die Falle der Operatorprioritäten geraten können, sehen Sie sich das dritte Beispiel an.) - Seien Sie sehr aufmerksam.
1.2. Implizite Konvertierungen
C ++ bezieht sich auf stark typisierte Sprachen, aber implizite Typkonvertierungen werden häufig verwendet, um Code kürzer zu machen. Diese impliziten Konvertierungen können in einigen Fällen zu Fehlern führen.
Die nervigsten impliziten Konvertierungen sind Konvertierungen eines numerischen Typs oder Zeigers auf bool
und von bool
nach int
. Es sind diese Transformationen (notwendig für die Kompatibilität mit C), die die in Abschnitt 1.1 beschriebenen Probleme verursachen. Implizite Konvertierungen, die möglicherweise zu einem Verlust der Genauigkeit numerischer Daten führen (Verengung der Konvertierungen), z. B. von double
nach int
sind ebenfalls nicht immer angemessen. In vielen Fällen generiert der Compiler eine Warnung (insbesondere wenn die numerischen Daten möglicherweise an Genauigkeit verlieren), eine Warnung ist jedoch kein Fehler. In C # sind Konvertierungen zwischen numerischen Typen und bool
verboten (auch explizit), und Konvertierungen, die möglicherweise zu Genauigkeitsverlusten bei numerischen Daten führen, sind fast immer ein Fehler.
Der Programmierer kann andere implizite Konvertierungen hinzufügen: (1) Definieren eines Konstruktors mit einem Parameter ohne das explicit
Schlüsselwort; (2) die Definition eines Typkonvertierungsoperators. Diese Transformationen schließen zusätzliche Sicherheitslücken, die auf starken Typisierungsprinzipien beruhen.
In C # ist die Anzahl der integrierten impliziten Konvertierungen viel geringer. Benutzerdefinierte implizite Konvertierungen müssen mit dem implicit
Schlüsselwort deklariert werden.
Wie man kämpft:
- Programm ohne Warnungen.
- Seien Sie sehr vorsichtig mit den oben beschriebenen Designs, verwenden Sie sie nicht ohne extreme Notwendigkeit.
2. Namensauflösung
2.1. Ausblenden von Variablen in verschachtelten Bereichen
In C ++ gilt die folgende Regel. Lass
Gemäß den C ++ - Regeln verbirgt die in
deklarierte Variable
die in
deklarierte Variable
Die erste Deklaration x
muss sich nicht in einem Block befinden: Sie kann Mitglied einer Klasse oder einer globalen Variablen sein, sie muss nur in Block
sichtbar sein
Stellen Sie sich jetzt die Situation vor, in der Sie den folgenden Code umgestalten müssen
Aus Versehen werden Änderungen vorgenommen:
Und jetzt macht der Code "etwas wird mit
von
" etwas mit
von
! Es ist klar, dass nicht alles wie zuvor funktioniert und es oft sehr schwierig ist, das zu finden. Es ist nicht umsonst, dass es in C # verboten ist, lokale Variablen auszublenden (obwohl Klassenmitglieder dies können). Beachten Sie, dass der Mechanismus zum Ausblenden von Variablen in der einen oder anderen Form in fast allen Programmiersprachen verwendet wird.
Wie man kämpft:
- Deklarieren Sie Variablen im engsten Bereich.
- Schreiben Sie keine langen und tief verschachtelten Blöcke.
- Verwenden Sie Codierungskonventionen, um Kennungen mit unterschiedlichem Gültigkeitsbereich visuell zu unterscheiden.
- Seien Sie sehr aufmerksam.
2.2. Funktionsüberlastung
Das Überladen von Funktionen ist ein wesentliches Merkmal vieler Programmiersprachen, und C ++ ist keine Ausnahme. Diese Gelegenheit muss jedoch sorgfältig genutzt werden, da Sie sonst in Schwierigkeiten geraten können. In einigen Fällen, zum Beispiel wenn der Konstruktor überladen ist, hat der Programmierer keine Wahl, aber in anderen Fällen kann die Verweigerung der Überladung gerechtfertigt sein. Berücksichtigen Sie die Probleme, die bei der Verwendung überladener Funktionen auftreten.
Wenn Sie versuchen, alle möglichen Optionen zu berücksichtigen, die sich beim Auflösen einer Überlastung ergeben können, sind die Regeln zum Auflösen einer Überlastung sehr kompliziert und daher schwer vorherzusagen. Zusätzliche Komplexität wird durch Vorlagenfunktionen und das Überladen integrierter Operatoren verursacht. C ++ 11 fügte Probleme mit rvalue-Links und Initialisierungslisten hinzu.
Der Suchalgorithmus kann Probleme für Kandidaten verursachen, um Überlastungen in verschachtelten Sichtbarkeitsbereichen zu beheben. Wenn der Compiler Kandidaten im aktuellen Bereich gefunden hat, wird die weitere Suche beendet. Wenn die gefundenen Kandidaten nicht geeignet, widersprüchlich, gelöscht oder unzugänglich sind, wird ein Fehler generiert, aber es wird keine weitere Suche versucht. Und nur wenn sich im aktuellen Bereich keine Kandidaten befinden, wechselt die Suche zum nächsten, breiteren Bereich. Der Mechanismus zum Ausblenden von Namen funktioniert fast genauso wie in Abschnitt 2.1, siehe [Dewhurst].
Überladungsfunktionen können die Lesbarkeit des Codes beeinträchtigen, was zu Fehlern führen kann.
Die Verwendung von Funktionen mit Standardparametern sieht aus wie die Verwendung überladener Funktionen, obwohl natürlich weniger potenzielle Probleme auftreten. Das Problem mit schlechter Lesbarkeit und möglichen Fehlern bleibt jedoch bestehen.
Mit äußerster Vorsicht sollten Überlast- und Standardparameter für virtuelle Funktionen verwendet werden, siehe Abschnitt 5.2.
C # unterstützt auch das Überladen von Funktionen, die Regeln zum Auflösen von Überladungen unterscheiden sich jedoch geringfügig.
Wie man kämpft:
- Missbrauche nicht die Überladung von Funktionen und entwerfe keine Funktionen mit Standardparametern.
- Wenn Funktionen überladen sind, verwenden Sie Signaturen, die beim Auflösen von Überladungen nicht zweifelhaft sind.
- Deklarieren Sie nicht gleichnamige Funktionen im verschachtelten Bereich.
- Vergessen Sie nicht, dass der in C ++ 11
=delete
Mechanismus der Remote-Funktionen ( =delete
) verwendet werden kann, um bestimmte Überlastungsoptionen zu verbieten.
3. Konstruktoren, Destruktoren, Initialisierung, Löschung
3.1. Vom Compiler generierte Klassenelementfunktionen
Wenn der Programmierer die Elementfunktionen der Klasse aus der folgenden Liste nicht definiert hat - Standardkonstruktor, Kopierkonstruktor, Kopierzuweisungsoperator, Destruktor -, kann der Compiler dies für ihn tun. C ++ 11 hat dieser Liste einen Verschiebungskonstruktor und einen Verschiebungszuweisungsoperator hinzugefügt. Diese Elementfunktionen werden als spezielle Elementfunktionen bezeichnet. Sie werden nur generiert, wenn sie verwendet werden, und zusätzliche Bedingungen, die für jede Funktion spezifisch sind, sind erfüllt. Wir machen darauf aufmerksam, dass sich diese Verwendung als ziemlich versteckt herausstellen kann (zum Beispiel bei der Implementierung der Vererbung). Wenn die erforderliche Funktion nicht generiert werden kann, wird ein Fehler generiert. (Mit Ausnahme von Verschiebungsvorgängen werden sie durch Kopiervorgänge ersetzt.) Die vom Compiler generierten Elementfunktionen sind öffentlich und können eingebettet werden. Details zu speziellen Mitgliedsfunktionen finden Sie in [Meyers2].
In einigen Fällen kann eine solche Hilfe des Compilers ein „Bärendienst“ sein. Das Fehlen benutzerdefinierter spezieller Elementfunktionen kann zur Erstellung eines trivialen Typs führen, was wiederum das Problem nicht initialisierter Variablen verursacht (siehe Abschnitt 3.2). Die generierten Elementfunktionen sind öffentlich und dies stimmt nicht immer mit dem Design der Klassen überein. In den Basisklassen muss der Konstruktor geschützt werden. Manchmal wird für eine genauere Kontrolle des Lebenszyklus des Objekts ein geschützter Destruktor benötigt. Wenn eine Klasse einen Rohressourcendeskriptor als Mitglied hat und diese Ressource besitzt, muss der Programmierer einen Kopierkonstruktor, einen Kopierzuweisungsoperator und einen Destruktor implementieren. Die sogenannte „Regel der großen Drei“ ist bekannt. Wenn ein Programmierer mindestens eine der drei Operationen definiert - Kopierkonstruktor, Kopierzuweisungsoperator oder Destruktor -, muss er alle drei Operationen definieren. Der Verschiebungskonstruktor und der vom Compiler generierte Verschiebungszuweisungsoperator sind bei weitem nicht immer das, was Sie benötigen. Der vom Compiler generierte Destruktor führt in einigen Fällen zu sehr subtilen Problemen, die zu einem Ressourcenleck führen können (siehe Abschnitt 3.7).
Der Programmierer kann die Generierung spezieller Elementfunktionen verbieten. In C ++ 11 muss beim Deklarieren das Konstrukt "=delete"
verwendet werden. In C ++ 98 muss die entsprechende Elementfunktion als privat deklariert und nicht definiert werden.
Wenn der Programmierer mit den vom Compiler generierten Elementfunktionen vertraut ist, kann er dies in C ++ 11 explizit angeben und nicht nur die Deklaration löschen. Dazu müssen Sie beim Deklarieren das Konstrukt "=default"
verwenden, während der Code besser gelesen wird und zusätzliche Funktionen im Zusammenhang mit der Verwaltung der Zugriffsebene "=default"
werden.
In C # kann der Compiler einen Standardkonstruktor generieren. Dies verursacht normalerweise keine Probleme.
Wie man kämpft:
- Steuern Sie den Compiler, der spezielle Elementfunktionen generiert. Implementieren Sie sie gegebenenfalls selbst oder verbieten Sie sie.
3.2. Nicht initialisierte Variablen
Konstruktoren und Destruktoren können als Schlüsselelemente des C ++ - Objektmodells bezeichnet werden. Beim Erstellen eines Objekts muss der Konstruktor aufgerufen werden, und beim Löschen wird der Destruktor aufgerufen. Kompatibilitätsprobleme mit C haben jedoch einige Ausnahmen erzwungen, und diese Ausnahme wird als triviale Typen bezeichnet. Sie werden eingeführt, um sichny-Typen und den systemischen Lebenszyklus von Variablen ohne den obligatorischen Aufruf des Konstruktors und Destruktors zu simulieren. Wenn C-Code in C ++ kompiliert und ausgeführt wird, sollte er genau wie in C funktionieren. Zu den Trivialtypen gehören numerische Typen, Zeiger, Aufzählungen sowie Klassen, Strukturen, Vereinigungen und Arrays, die aus Trivialtypen bestehen. Klassen und Strukturen müssen einige zusätzliche Bedingungen erfüllen: das Fehlen eines benutzerdefinierten Konstruktors, Destruktors, Kopierers und virtueller Funktionen. Für eine triviale Klasse kann der Compiler einen Standardkonstruktor und einen Destruktor generieren. Der Standardkonstruktor setzt das Objekt auf Null, der Destruktor tut nichts. Dieser Konstruktor wird jedoch nur generiert und verwendet, wenn er bei der Initialisierung der Variablen explizit aufgerufen wird. Eine Variable eines trivialen Typs wird nicht initialisiert, wenn Sie keine Variante der expliziten Initialisierung verwenden. Die Initialisierungssyntax hängt vom Typ und Kontext der Variablendeklaration ab. Statische und lokale Variablen werden beim Deklarieren initialisiert. Für eine Klasse werden unmittelbare Basisklassen und nicht statische Klassenmitglieder in der Konstruktorinitialisierungsliste initialisiert. (Mit C ++ 11 können Sie nicht statische Klassenmitglieder beim Deklarieren initialisieren (siehe später).) Bei dynamischen Objekten erstellt der Ausdruck new T()
ein vom Standardkonstruktor initialisiertes Objekt, bei trivialem Typ jedoch ein new T
für nicht initialisierte Objekte. Wenn Sie ein dynamisches Array eines trivialen Typs, new T[N]
, erstellen, werden seine Elemente immer nicht initialisiert. Wenn eine Instanz von std::vector<T>
erstellt oder erweitert wird und keine Parameter für die explizite Initialisierung von Elementen bereitgestellt werden, wird garantiert, dass sie den Standardkonstruktor aufrufen. C ++ 11 führt eine neue Initialisierungssyntax ein - mit geschweiften Klammern. Ein leeres Klammerpaar bedeutet die Initialisierung mit dem Standardkonstruktor. Eine solche Initialisierung ist überall dort möglich, wo die traditionelle Initialisierung verwendet wird. Außerdem wurde es möglich, nicht statische Mitglieder der Klasse beim Deklarieren zu initialisieren, was die Initialisierung in der Konstruktorinitialisierungsliste ersetzt.
Eine nicht initialisierte Variable ist wie folgt strukturiert: Wenn sie im namespace
Bereich (global) definiert ist, hat sie alle Bits Null. Wenn sie lokal oder dynamisch erstellt wird, erhält sie einen zufälligen Satz von Bits. Es ist klar, dass die Verwendung einer solchen Variablen zu einem unvorhersehbaren Verhalten des Programms führen kann.
Zwar steht der Fortschritt nicht still, moderne Compiler erkennen in einigen Fällen nicht initialisierte Variablen und werfen einen Fehler aus. Nicht initialisierte Codeanalysatoren erkennen noch besser.
Die C ++ 11-Standardbibliothek verfügt über Vorlagen, die als <type_traits>
(Header-Datei <type_traits>
). Mit einer davon können Sie feststellen, ob der Typ trivial ist. Der Ausdruck std::is_trivial<>::value
ist true
wenn T
trivialer Typ ist, andernfalls false
.
Sysylische Strukturen werden oft auch als Plain Old Data (POD) bezeichnet. Wir können davon ausgehen, dass POD und der „triviale Typ“ fast gleichwertige Begriffe sind.
In C # verursachen nicht initialisierte Variablen einen Fehler, der vom Compiler gesteuert wird. Felder von Objekten eines Referenztyps werden standardmäßig initialisiert, wenn keine explizite Initialisierung durchgeführt wird. Felder von Objekten eines signifikanten Typs werden entweder standardmäßig alle initialisiert oder alle müssen explizit initialisiert werden.
Wie man kämpft:
- Haben Sie die Angewohnheit, eine Variable explizit zu initialisieren. Eine nicht initialisierte Variable sollte "das Auge schneiden".
- Deklarieren Sie Variablen im engsten Bereich.
- Verwenden Sie statische Code-Analysatoren.
- Entwerfen Sie keine trivialen Typen. Um sicherzustellen, dass der Typ nicht trivial ist, reicht es aus, einen benutzerdefinierten Konstruktor zu definieren.
3.3. Initialisierungsverfahren für Basisklassen und nicht statische Klassenmitglieder
Bei der Implementierung des Klassenkonstruktors werden unmittelbare Basisklassen und nicht statische Klassenmitglieder initialisiert. Die Initialisierungsreihenfolge wird durch den Standard bestimmt: Zuerst die Basisklassen in der Reihenfolge, in der sie in der Liste der Basisklassen deklariert sind, dann nicht statische Mitglieder der Klasse in der Deklarationsreihenfolge. Bei Bedarf wird für die explizite Initialisierung von Basisklassen und nicht statischen Elementen die Konstruktorinitialisierungsliste verwendet. Leider müssen die Elemente in dieser Liste nicht in der Reihenfolge sein, in der die Initialisierung erfolgt. Dies muss berücksichtigt werden, wenn Listenelemente während der Initialisierung Verweise auf andere Listenelemente verwenden. Bei einem Fehler kann die Verknüpfung zu einem Objekt bestehen, das noch nicht initialisiert wurde. Mit C ++ 11 können Sie nicht statische Klassenmitglieder beim Deklarieren initialisieren (mit geschweiften Klammern). In diesem Fall müssen sie nicht in der Konstruktorinitialisierungsliste initialisiert werden, und das Problem wird teilweise behoben.
In C # wird ein Objekt wie folgt initialisiert: Zuerst werden die Felder vom Basis-Unterobjekt bis zur letzten Ableitung initialisiert, dann werden die Konstruktoren in derselben Reihenfolge aufgerufen. Das beschriebene Problem tritt nicht auf.
Wie man kämpft:
- Pflegen Sie die Konstruktorinitialisierungsliste in Deklarationsreihenfolge.
- Versuchen Sie, die Initialisierung von Basisklassen und Klassenmitgliedern unabhängig zu machen.
- Verwenden Sie beim Deklarieren die Initialisierung nicht statischer Elemente.
3.4. Initialisierungsverfahren für statische Klassenmitglieder und globale Variablen
Statische Klassenmitglieder sowie Variablen, die im Bereichsnamespace (global) in verschiedenen Kompilierungseinheiten (Dateien) definiert sind, werden in der von der Implementierung festgelegten Reihenfolge initialisiert. Dies sollte berücksichtigt werden, wenn solche Variablen während der Initialisierung Verweise aufeinander verwenden. Die Verknüpfung kann zu einer nicht initialisierten Variablen bestehen.
Wie man kämpft:
- Treffen Sie besondere Maßnahmen, um diese Situation zu verhindern. Verwenden Sie beispielsweise lokale statische Variablen (Singleton), die bei der ersten Verwendung initialisiert werden.
3.5. Ausnahmen bei Destruktoren
Der Destruktor sollte keine Ausnahmen auslösen. Wenn Sie gegen diese Regel verstoßen, kann es zu undefiniertem Verhalten kommen, meistens zu abnormaler Beendigung.
Wie man kämpft:
- Vermeiden Sie es, Ausnahmen in den Destruktor zu werfen.
3.6. Dynamische Objekte und Arrays entfernen
Wenn ein dynamisches Objekt vom Typ T
T* pt = new T();
dann wird es mit dem delete
gelöscht
delete pt;
Wenn ein dynamisches Array erstellt wird
T* pt = new T[N];
dann wird es mit dem Operator delete[]
gelöscht
delete[] pt;
Wenn Sie diese Regel nicht befolgen, kann es zu undefiniertem Verhalten kommen, dh es kann alles passieren: ein Speicherverlust, ein Absturz usw. Siehe [Meyers1] für Details.
Wie man kämpft:
- Verwenden Sie das richtige
delete
.
3.7. Löschen, wenn die Klassendeklaration unvollständig ist
Die Allesfresserhaftigkeit des delete
kann bestimmte Probleme verursachen, sie kann auf einen Zeiger vom Typ void*
oder auf einen Zeiger auf eine Klasse angewendet werden, die eine unvollständige (präemptive) Deklaration aufweist. Der delete
, der auf einen Zeiger auf eine Klasse angewendet wird, ist eine zweiphasige Operation: Zuerst wird der Destruktor aufgerufen, dann wird Speicher freigegeben.Wenn der Operator delete
auf einen Zeiger auf eine Klasse mit einer unvollständigen Deklaration angewendet wird , tritt kein Fehler auf. Der Compiler überspringt einfach den Aufruf an den Destruktor (obwohl eine Warnung ausgegeben wird). Betrachten Sie ein Beispiel:
class X;
Dieser Code wird auch dann kompiliert, wenn die delete
vollständige Klassendeklaration beim Dial-Peer nicht verfügbar ist X
. Visual Studio zeigt die folgende Warnung an:warning C4150: deletion of pointer to incomplete type 'X'; no destructor called
Wenn es eine Implementierung gibt X
und CreateX()
der Code kompiliert wird, wenn er CreateX()
einen Zeiger auf ein vom Operator erstelltes Objekt zurückgibt new
, der Aufruf Foo()
erfolgreich ausgeführt wird, wird der Destruktor nicht aufgerufen. Es ist klar, dass dies zu einem Ressourcenverbrauch führen kann, also noch einmal über die Notwendigkeit, vorsichtig mit Warnungen umzugehen.
, -. , . , , , , . [Meyers2].
:
4. ,
4.1.
++ , . . . , 1.1.
Hier ist ein Beispiel:
std::out<<c?x:y;
(std::out<<c)?x:y;
std::out<<(c?x:y);
, , .
. <<
?:
std::out
void*
. ++ , . -, , . ?:
. , ( ).
: x&f==0
x&(f==0)
, (x&f)==0
, , , . - , , , , .
. / . / , /, . , x/4+1
x>>2+1
, x>>(2+1)
, (x>>2)+1
, .
C# , C++, , - .
:
4.2.
++ , . . , , . 4.1. — +
+=
. . , : ,
(), &&
, ||
. , (-), (short-circuit evaluation semantics), , . & ( ). & , .. .
, - (-) , . .
- , , . . [Dewhurst].
C# , , , .
:
4.3.
++ , . ( : ,
(), &&
, ||
, ?:
.) , , , . :
int x=0; int y=(++x*2)+(++x*3);
y
.
, . Hier ist ein Beispiel.
class X; class Y; void Foo(std::shared_ptr<X>, std::shared_ptr<Y>);
Foo()
:
Foo(std::shared_ptr<X>(new X()), std::shared_ptr<Y>(new Y()));
: X
, Y
, std::shared_ptr<X>
, std::shared_ptr<Y>
. Y
, X
.
:
auto p1 = std::shared_ptr<X>(new X()); auto p2 = std::shared_ptr<Y>(new Y()); Foo(p1, p2);
std::make_shared<Y>
( , ):
Foo(std::make_shared<X>(), std::make_shared<Y>());
. [Meyers2].
:
5.
5.1.
++98 , ( ), , ( , ). virtual
, , . ( ), , , . , , . , ++11 override
, , , . .
:
5.2.
. , , . . . [Dewhurst].
:
5.3.
, , . , , post_construct pre_destroy. , — . . , : ( ) . (, , .) , ( ), ( ). . [Dewhurst]. , , .
— - .
, C# , , , . C# : , , . , ( , ).
:
5.4.
, , delete
. , - .
:
6.
— C/C++, . . . « ».
C# unsafe mode, .
6.1.
/++ , : strcpy()
, strcat()
, sprinf()
, etc. ( std::vector<>
, etc.) , . (, , , . . Checked Iterators MSDN.) , : , , ; , .
C#, unsafe mode, .
:
- , .
- .
- z-terminated ,
_s
(. ).
6.2. Z-terminated
, . , :
strncpy(dst,src,n);
strlen(src)>=n
, dst
(, ). , , . . — . if(*str)
, if(strlen(str)>0)
, . [Spolsky].
C# string
.
:
6.3.
...
. printf
- , C. , , , , . , .
C# printf
, .
:
7.
7.1.
++ , , , . Hier ist ein Beispiel:
const int N = 4, M = 6; int x,
:
int
;int
;N
int
;N
int
;- ,
char
int
; - ,
char
int
; - ,
char
int
; N
, char
int
;N
int
;M
N
int
;- ,
char
, long
int
.
, . ( .)
*
&
. ( .)
typedef
( using
-). , :
typedef int(*P)(long); PH(char);
, .
C# , .
:
7.2.
.
class X { public: X(int val = 0);
X x(5);
x
X
, 5.
X x();
x
, X
, x
X
, . X
, , :
X x; X x = X(); X x{};
, , , . [Sutter].
, , C++ ( ). . ( C++ .)
, , , , .
C# , , .
:
8.
8.1. inline
ODR
, inline
— . , . inline
(One Defenition Rule, ODR). . , . , ODR. static
: , , . static
inline
. , , ODR, . , . - , -. .
:
- «»
inline
. namespace
. , . - —
namespace
.
8.2.
. . , , , , .
:
- , .
- , : () , -.
using
-: using namespace
, using
-.- .
8.3. switch
— break
case
. ( .) C# .
:
8.4.
++ , — , — . ( class
struct
) , . ( , # Java.) — , .
- , . (
std::string
, std::vector
, etc.), , . - , , .
- , (slicing), , .
, , , . . , , . , . . — ( =delete
), — explicit
.
C# , .
:
8.5. Ressourcenmanagement
++ . , . - ( ), ++11 , , , .
C++ .
C# , . , . (using-) Basic Dispose.
:
8.6.
«» . , , C++ , STL- - .
. . , . . «», . COM- . (, .) , C++ . — . . . , («» ) , . .
# , . — .
:
8.7.
C++ , : , , . ( !) . , . , . , , . (, .)
C ( ), C++ C ( extern "C"
). C/C++ .
-. #pragma
- , , .
, , , .
, , COM. COM-, , ( , ). COM , , .
C# . , — , C#, C# C/C++.
:
8.8.
, . , . C++ . Stattdessen
#define XXL 32
const int XXL=32;
. inline
.
# ( ).
:
9.
- . . . , .
- .
- . ++ — ++11/14/17.
- - , - .
- .
Referenzliste
Liste[Dewhurst]
, . C++. .: . . — .: , 2012.
[Meyers1]
, . C++. 55 .: . . — .: , 2014.
[Meyers2]
, . C++: 42 C++11 C++14.: . . — .: «.. », 2016.
[Sutter]
, . C++.: . . — : «.. », 2015.
[Spolsky]
, . .: . . — .: -, 2008.