Versteckte Funktionen von C ++? [geschlossen]


114

Keine C ++ Liebe, wenn es um die "versteckten Funktionen von" Fragen geht? Ich dachte, ich würde es da rauswerfen. Was sind einige der versteckten Funktionen von C ++?


@ Devtron - Ich habe einige großartige Fehler (dh unerwartetes Verhalten) gesehen, die als Features verkauft wurden. Tatsächlich versucht die Spieleindustrie heutzutage tatsächlich, dies zu erreichen und nennt es "aufstrebendes Gameplay" (siehe auch "TK Surfing" von Psi-Ops, war nur ein Fehler, dann ließen sie es so wie es ist und es ist eines der beste Eigenschaften des Spiels IMHO)
Grant Peters

5
@Laith J: Nicht sehr viele Leute haben den 786-seitigen ISO C ++ - Standard von Anfang bis Ende gelesen - aber ich nehme an, Sie haben alles behalten, oder?
j_random_hacker

2
@Laith, @j_random: Siehe meine Frage "Was ist ein Programmierwitz, wie erkenne ich ihn und was ist die richtige Antwort" unter stackoverflow.com/questions/1/you-have-been-link-rolled .

Antworten:


308

Die meisten C ++ - Programmierer kennen den ternären Operator:

x = (y < 0) ? 10 : 20;

Sie erkennen jedoch nicht, dass es als Wert verwendet werden kann:

(a == 0 ? a : b) = 1;

Das ist eine Abkürzung für

if (a == 0)
    a = 1;
else
    b = 1;

Vorsichtig verwenden :-)


11
Sehr interessant. Ich kann sehen, dass ein unlesbarer Code entsteht.
Jason Baker

112
Huch. (a == 0? a: b) = (y <0? 10: 20);
Jasper Bekkers

52
(b? trueCount: falseCount) ++
Pavel Radzivilovsky

12
Keine Ahnung, ob es GCC-spezifisch ist, aber ich war überrascht, dass dies auch funktioniert hat : (value ? function1 : function2)().
Chris Burt-Brown

3
@ Chris Burt-Brown: Nein, das sollte überall funktionieren, wenn sie denselben Typ haben (dh keine Standardargumente ) function1und function2implizit in Funktionszeiger konvertiert werden und das Ergebnis implizit zurückkonvertiert wird.
MSalters

238

Sie können URIs fehlerfrei in eine C ++ - Quelle einfügen. Beispielsweise:

void foo() {
    http://stackoverflow.com/
    int bar = 4;

    ...
}

41
Aber nur eine pro Funktion, vermute ich? :)
Constantin

51
@jpoh: http gefolgt von einem Doppelpunkt wird zu einem "Label", das Sie später in einer goto-Anweisung verwenden. Sie erhalten diese Warnung von Ihrem Compiler, da sie im obigen Beispiel in keiner goto-Anweisung verwendet wird.
utku_karatas

9
Sie können mehrere hinzufügen, solange sie unterschiedliche Protokolle haben! ftp.microsoft.com gopher: //aerv.nl/1 und so weiter ...
Daniel Earwicker

4
@Pavel: Ein Bezeichner gefolgt von einem Doppelpunkt ist eine Bezeichnung (zur Verwendung mit goto, die C ++ hat). Alles, was nach zwei Schrägstrichen folgt, ist ein Kommentar. Daher mit http://stackoverflow.com, httpist ein Label (man könnte theoretisch schreiben goto http;), und //stackoverflow.comist nur ein End-of-Line - Kommentar. Beide sind legales C ++, daher wird das Konstrukt kompiliert. Es macht natürlich nichts vage Nützliches.
David Thornley

8
goto http;Folgt leider nicht wirklich der URL. :(
Yakov Galka

140

Zeigerarithmetik.

C ++ - Programmierer bevorzugen es, Zeiger zu vermeiden, da Fehler auftreten können.

Das coolste C ++, das ich je gesehen habe? Analoge Literale.


11
Wir vermeiden Zeiger wegen Fehlern? Zeiger sind im Grunde alles, worum es bei dynamischer C ++ - Codierung geht!
Nick Bedford

1
Analoge Literale eignen sich hervorragend für verschleierte C ++ - Wettbewerbsbeiträge, insbesondere für den ASCII-Art-Typ.
Synetech

119

Ich stimme den meisten Posts dort zu: C ++ ist eine Multi-Paradigma-Sprache, daher sind die "versteckten" Funktionen, die Sie finden (außer "undefinierten Verhaltensweisen", die Sie unbedingt vermeiden sollten), eine clevere Verwendung von Einrichtungen.

Die meisten dieser Einrichtungen sind keine integrierten Funktionen der Sprache, sondern bibliotheksbasierte.

Das wichtigste ist das RAII , das von C ++ - Entwicklern aus der C-Welt oft jahrelang ignoriert wird. Das Überladen von Operatoren ist häufig eine missverstandene Funktion, die sowohl Array-ähnliches Verhalten (Indexoperator) als auch zeigerähnliche Operationen (intelligente Zeiger) und integrierte Operationen (Multiplikation von Matrizen) ermöglicht.

Die Verwendung von Ausnahmen ist oft schwierig, kann jedoch mit etwas Arbeit durch Ausnahmesicherheit wirklich robusten Code erzeugen (einschließlich Code, der nicht fehlschlägt oder über Commit-ähnliche Funktionen verfügt, die erfolgreich sind oder zu denen zurückgesetzt wird) seinen ursprünglichen Zustand).

Die bekannteste "versteckte" Funktion von C ++ ist die Vorlagen-Metaprogrammierung , da Sie Ihr Programm teilweise (oder vollständig) zur Kompilierungszeit anstatt zur Laufzeit ausführen lassen können. Dies ist jedoch schwierig, und Sie müssen die Vorlagen genau kennen, bevor Sie sie ausprobieren können.

Andere nutzen das multiple Paradigma, um "Programmierweisen" außerhalb des Vorfahren von C ++, dh C, zu erzeugen.

Durch die Verwendung von Funktoren können Sie Funktionen simulieren, mit der zusätzlichen Typensicherheit und dem Status. Mit dem Befehlsmuster können Sie die Codeausführung verzögern. Die meisten anderen Entwurfsmuster können einfach und effizient in C ++ implementiert werden, um alternative Codierungsstile zu erstellen, die nicht in der Liste der "offiziellen C ++ - Paradigmen" enthalten sein sollen.

Mithilfe von Vorlagen können Sie Code erstellen, der für die meisten Typen geeignet ist, auch nicht für den, an den Sie zuerst gedacht haben. Sie können auch die Typensicherheit erhöhen (wie bei einem automatisierten typsicheren Malloc / Realloc / Free). C ++ - Objektfunktionen sind sehr leistungsfähig (und daher gefährlich, wenn sie unachtsam verwendet werden), aber selbst der dynamische Polymorphismus hat seine statische Version in C ++: das CRTP .

Ich habe festgestellt, dass die meisten Bücher vom Typ " Effective C ++ " von Scott Meyers oder Bücher vom Typ " Exceptional C ++ " von Herb Sutter sowohl leicht zu lesen sind als auch eine Fülle von Informationen über bekannte und weniger bekannte Funktionen von C ++ enthalten.

Unter meinen bevorzugten ist eine, die die Haare eines jeden Java-Programmierers vor Entsetzen erheben sollte: In C ++ ist die objektorientierteste Möglichkeit, einem Objekt eine Funktion hinzuzufügen , die Funktion eines Nichtmitglieds anstelle eines Mitglieds. Funktion (dh Klassenmethode), weil:

  • In C ++ besteht die Schnittstelle einer Klasse sowohl aus ihren Member-Funktionen als auch aus den Nicht-Member-Funktionen im selben Namespace

  • Nicht-Freund-Nicht-Mitglied-Funktionen haben keinen privilegierten Zugriff auf die interne Klasse. Wenn Sie also eine Member-Funktion über eine Nicht-Member-Non-Friend-Funktion verwenden, wird die Kapselung der Klasse geschwächt.

Dies überrascht selbst erfahrene Entwickler immer wieder.

(Quelle: Unter anderem Herb Sutters Online-Guru der Woche Nr. 84: http://www.gotw.ca/gotw/084.htm )


+1 sehr gründliche Antwort. Es ist aus offensichtlichen Gründen unvollständig (sonst gäbe es keine "versteckten Funktionen" mehr!): p Im ersten Punkt am Ende der Antwort haben Sie Mitglieder einer Klassenschnittstelle erwähnt. meinst du ".. ist sowohl seine Mitgliedsfunktionen als auch die Freund- Nicht-Mitgliederfunktionen"?
Wilhelmtell


Was Sie mit 1 erwähnen, muss koenig Lookup sein, oder?
Özgür

1
@wilhelmtell: Nein, nein, nein ... :-p ... Ich meine "seine Mitgliedsfunktionen und NICHT-FREUNDLICHE Nicht-Mitgliederfunktionen" .... Koenigs Lookup stellt sicher, dass diese Funktionen früher als andere berücksichtigt werden. " außerhalb "funktioniert bei der Suche nach Symbolen
paercebal

7
Toller Beitrag und +1 speziell für den letzten Teil, den viel zu wenige Leute realisieren. Ich würde wahrscheinlich auch die Boost-Bibliothek als "verstecktes Feature" hinzufügen. Ich halte es für die Standardbibliothek, die C ++ hätte haben sollen. ;)
Jalf

118

Ein Sprachmerkmal, das ich für etwas versteckt halte, weil ich während meiner gesamten Schulzeit noch nie davon gehört hatte, ist der Namespace-Alias. Ich wurde erst darauf aufmerksam gemacht, als ich in der Boost-Dokumentation auf Beispiele dafür stieß. Jetzt, da ich davon weiß, können Sie es natürlich in jeder Standard-C ++ - Referenz finden.

namespace fs = boost::filesystem;

fs::path myPath( strPath, fs::native );

1
Ich denke, das ist nützlich, wenn Sie nicht verwenden möchten using.
Siqi Lin

4
Es ist auch nützlich, um zwischen Implementierungen zu wechseln, unabhängig davon, ob Sie thread-sicher oder nicht thread-sicher oder Version 1 oder 2 auswählen.
Tony Delroy

3
Dies ist besonders nützlich, wenn Sie an einem sehr großen Projekt mit großen Namespace-Hierarchien arbeiten und nicht möchten, dass Ihre Header eine Verschmutzung des Namespace verursachen (und Ihre Variablendeklarationen für Menschen lesbar sind).
Brandon Bohrer

102

Im init-Teil einer forSchleife können nicht nur Variablen deklariert werden , sondern auch Klassen und Funktionen.

for(struct { int a; float b; } loop = { 1, 2 }; ...; ...) {
    ...
}

Dies ermöglicht mehrere Variablen unterschiedlichen Typs.


31
Schön zu wissen, dass Sie das können, aber ich persönlich würde wirklich versuchen, so etwas zu vermeiden. Meistens, weil es schwer zu lesen ist.
Zoomulator

2
In diesem Zusammenhang würde tatsächlich ein Paar verwendet werden: for (std :: pair <int, float> loop = std :: make_pair (1,2); loop.first> 0; loop.second + = 1)
Valentin Heinitz

2
@Valentin Nun, dann empfehle ich Ihnen, einen Fehlerbericht gegen VS2008 zu erstellen, anstatt die versteckte Funktion herunterzustimmen. Es ist eindeutig die Schuld Ihres Compilers.
Johannes Schaub - Litb

2
Hmm, es funktioniert auch nicht in msvc10. Wie traurig :(
Avakar

2
@avakar in der Tat, gcc hat einen Fehler eingeführt, der es auch in v4.6 ablehnen lässt :) siehe gcc.gnu.org/bugzilla/show_bug.cgi?id=46791
Johannes Schaub - litb

77

Der Array-Operator ist assoziativ.

A [8] ist ein Synonym für * (A + 8). Da Addition assoziativ ist, kann dies als * (8 + A) umgeschrieben werden, was ein Synonym für ..... 8 [A] ist.

Du hast nicht nützlich gesagt ... :-)


15
Wenn Sie diesen Trick verwenden, sollten Sie wirklich darauf achten, welchen Typ Sie verwenden. A [8] ist eigentlich das 8. A, während 8 [A] die Ath-Ganzzahl ab Adresse 8 ist. Wenn A ein Byte ist, liegt ein Fehler vor.
Vincent Robert

38
du meinst "kommutativ" wo du "assoziativ" sagst?
DarenW

28
Vincent, du liegst falsch. Die Art spielt Aüberhaupt keine Rolle. Wenn zum Beispiel Aein sind char*, würde der Code immer noch gültig sein.
Konrad Rudolph

11
Beachten Sie, dass A ein Zeiger und kein Klassenüberladungsoperator sein muss [].
David Rodríguez - Dribeas

15
Vincent, hier muss es einen Integraltyp und einen Zeigertyp geben, und weder C noch C ++ kümmern sich darum, welcher zuerst geht.
David Thornley

73

Eine Sache, die wenig bekannt ist, ist, dass Gewerkschaften auch Vorlagen sein können:

template<typename From, typename To>
union union_cast {
    From from;
    To   to;

    union_cast(From from)
        :from(from) { }

    To getTo() const { return to; }
};

Und sie können auch Konstruktoren und Elementfunktionen haben. Nur nichts, was mit Vererbung zu tun hat (einschließlich virtueller Funktionen).


Interessant! Müssen Sie also alle Mitglieder initialisieren? Entspricht es der üblichen Strukturreihenfolge, was bedeutet, dass das letzte Mitglied "über" früheren Mitgliedern initialisiert wird?
j_random_hacker

j_random_hacker oh, richtig, das ist Unsinn. guter Fang. Ich habe es so geschrieben, als wäre es eine Struktur. Warten Sie, ich werde es reparieren
Johannes Schaub - Litb

Ruft dies nicht undefiniertes Verhalten hervor?
Greg Bacon

7
@gbacon, ja, es ruft undefiniertes Verhalten auf, wenn Fromund Toentsprechend gesetzt und verwendet werden. Eine solche Vereinigung kann jedoch mit definiertem Verhalten verwendet werden (wobei Toes sich um ein Array von Zeichen ohne Vorzeichen oder eine Struktur handelt, mit der eine Anfangssequenz geteilt wird From). Selbst wenn Sie es undefiniert verwenden, kann es für Arbeiten auf niedriger Ebene nützlich sein. Auf jeden Fall ist dies nur ein Beispiel für eine Gewerkschaftsvorlage - es kann andere Verwendungszwecke für eine Vorlagenvereinigung geben.
Johannes Schaub - litb

3
Vorsicht mit dem Konstrukteur. Beachten Sie, dass Sie nur das erste Element erstellen müssen und dies nur in C ++ 0x zulässig ist. Nach dem aktuellen Standard müssen Sie sich an trivial konstruierbare Typen halten. Und keine Zerstörer.
Potatoswatter

72

C ++ ist ein Standard, es sollte keine versteckten Funktionen geben ...

C ++ ist eine Multi-Paradigmen-Sprache. Sie können Ihr letztes Geld darauf setzen, dass es versteckte Funktionen gibt. Ein Beispiel von vielen: Template-Metaprogrammierung . Niemand im Normungsausschuss beabsichtigte, dass es eine Turing-vollständige Subsprache geben sollte, die zur Kompilierungszeit ausgeführt wird.


65

Eine weitere versteckte Funktion, die in C nicht funktioniert, ist die Funktionalität des unären +Operators. Sie können es verwenden, um alle möglichen Dinge zu fördern und zu verfallen

Konvertieren einer Aufzählung in eine Ganzzahl

+AnEnumeratorValue

Und Ihr Enumeratorwert, der zuvor seinen Aufzählungstyp hatte, hat jetzt den perfekten Ganzzahltyp, der zu seinem Wert passt. Manuell würde man diesen Typ kaum kennen! Dies ist beispielsweise erforderlich, wenn Sie einen überladenen Operator für Ihre Aufzählung implementieren möchten.

Holen Sie sich den Wert aus einer Variablen

Sie müssen eine Klasse verwenden, die einen statischen Initialisierer innerhalb der Klasse ohne Definition außerhalb der Klasse verwendet, aber manchmal keine Verknüpfung herstellt. Der Operator kann dabei helfen, eine temporäre Datei zu erstellen, ohne Annahmen oder Abhängigkeiten von ihrem Typ zu treffen

struct Foo {
  static int const value = 42;
};

// This does something interesting...
template<typename T>
void f(T const&);

int main() {
  // fails to link - tries to get the address of "Foo::value"!
  f(Foo::value);

  // works - pass a temporary value
  f(+Foo::value);
}

Zerlegen Sie ein Array in einen Zeiger

Möchten Sie zwei Zeiger an eine Funktion übergeben, aber es funktioniert einfach nicht? Der Bediener kann helfen

// This does something interesting...
template<typename T>
void f(T const& a, T const& b);

int main() {
  int a[2];
  int b[3];
  f(a, b); // won't work! different values for "T"!
  f(+a, +b); // works! T is "int*" both time
}

61

Die Lebensdauer von Provisorien, die an konstante Referenzen gebunden sind, ist eine, die nur wenige Menschen kennen. Zumindest ist es mein Lieblingswissen in C ++, von dem die meisten Leute nichts wissen.

const MyClass& x = MyClass(); // temporary exists as long as x is in scope

3
Können Sie das näher erläutern? Wie ist es nur necken;)
Joseph Garvin

8
ScopeGuard ( ddj.com/cpp/184403758 ) ist ein hervorragendes Beispiel, das diese Funktion nutzt.
MSN

2
Ich bin bei Joseph Garvin. Bitte klären Sie uns auf.
Peter Mortensen

Ich habe es gerade in den Kommentaren getan. Außerdem ist es eine natürliche Folge der Verwendung eines konstanten Referenzparameters.
MSN


52

Eine nette Funktion, die nicht oft verwendet wird, ist der funktionsweite Try-Catch-Block:

int Function()
try
{
   // do something here
   return 42;
}
catch(...)
{
   return -1;
}

Die Hauptverwendung wäre, eine Ausnahme in eine andere Ausnahmeklasse zu übersetzen und erneut zu werfen oder zwischen Ausnahmen und einer auf Rückgabe basierenden Fehlercodebehandlung zu übersetzen.


Ich glaube nicht, dass Sie returnaus dem Catch-Block von Function Try nur erneut werfen können.
Constantin

Ich habe gerade versucht, das oben genannte zu kompilieren, und es gab keine Warnung. Ich denke, das obige Beispiel funktioniert.
Vividos

7
Die Rückgabe ist nur für Konstrukteure verboten. Der Funktionsversuchsblock eines Konstruktors erkennt Fehler beim Initialisieren der Basis und der Mitglieder (der einzige Fall, in dem ein Funktionsversuchsblock etwas anderes tut als nur einen Versuch innerhalb der Funktion durchzuführen). Ein erneutes Werfen würde zu einem unvollständigen Objekt führen.
Puetzk

Ja. Das ist sehr nützlich. Ich habe die Makros BEGIN_COM_METHOD und END_COM_METHOD geschrieben, um Ausnahmen abzufangen und HRESULTS zurückzugeben, damit keine Ausnahmen aus einer Klasse austreten, die eine COM-Schnittstelle implementiert. Es hat gut funktioniert.
Scott Langham

3
Wie von @puetzk hervorgehoben, ist dies die einzige Möglichkeit, Ausnahmen zu behandeln, die von irgendetwas in der Initialisiererliste eines Konstruktors ausgelöst werden , wie z. B. den Konstruktoren der Basisklassen oder denen von Datenelementen.
anton.burger

44

Viele kennen die identity/ idmetafunktion, aber es gibt einen guten Anwendungsfall für Fälle ohne Vorlage: Einfaches Schreiben von Erklärungen:

// void (*f)(); // same
id<void()>::type *f;

// void (*f(void(*p)()))(int); // same
id<void(int)>::type *f(id<void()>::type *p);

// int (*p)[2] = new int[10][2]; // same
id<int[2]>::type *p = new int[10][2];

// void (C::*p)(int) = 0; // same
id<void(int)>::type C::*p = 0;

Es hilft sehr, C ++ - Deklarationen zu entschlüsseln!

// boost::identity is pretty much the same
template<typename T> 
struct id { typedef T type; };

Interessant, aber anfangs hatte ich tatsächlich mehr Probleme, einige dieser Definitionen zu lesen. Eine andere Möglichkeit, das Inside-Out-Problem mit C ++ - Deklarationen zu beheben, besteht darin, einige Aliase vom Typ Vorlage zu schreiben: template<typename Ret,typename... Args> using function = Ret (Args...); template<typename T> using pointer = *T;-> pointer<function<void,int>> f(pointer<function<void,void>>);oder pointer<void(int)> f(pointer<void()>);oderfunction<pointer<function<void,int>>,pointer<function<void,void>>> f;
bames53

42

Eine ziemlich versteckte Funktion ist, dass Sie Variablen innerhalb einer if-Bedingung definieren können und ihr Bereich sich nur über die if- und else-Blöcke erstreckt:

if(int * p = getPointer()) {
    // do something
}

Einige Makros verwenden dies, um beispielsweise einen "gesperrten" Bereich wie diesen bereitzustellen:

struct MutexLocker { 
    MutexLocker(Mutex&);
    ~MutexLocker(); 
    operator bool() const { return false; } 
private:
    Mutex &m;
};

#define locked(mutex) if(MutexLocker const& lock = MutexLocker(mutex)) {} else 

void someCriticalPath() {
    locked(myLocker) { /* ... */ }
}

Auch BOOST_FOREACH benutzt es unter der Haube. Um dies zu vervollständigen, ist es nicht nur in einem if möglich, sondern auch in einem Switch:

switch(int value = getIt()) {
    // ...
}

und in einer while-Schleife:

while(SomeThing t = getSomeThing()) {
    // ...
}

(und auch in einem Zustand). Aber ich bin mir nicht sicher, ob das alles so nützlich ist :)


Ordentlich! Ich hätte nie gedacht, dass Sie das tun könnten ... es hätte (und wird) Ärger sparen, wenn Sie Code mit Fehlerrückgabewerten schreiben. Gibt es eine Möglichkeit, in dieser Form immer noch eine Bedingung statt nur! = 0 zu haben? if ((int r = func ()) <0) scheint nicht zu funktionieren ...
puetzk

Puetzk, nein gibt es nicht. aber froh, dass es dir gefällt :)
Johannes Schaub - litb

4
@Frerich, das ist im C-Code überhaupt nicht möglich. Ich denke, Sie denken darüber nach if((a = f()) == b) ..., aber diese Antwort deklariert tatsächlich eine Variable in der Bedingung.
Johannes Schaub - Litb

1
@Angry ist ganz anders, weil die Variablendeklaration sofort auf ihren booleschen Wert getestet wird. Es gibt auch eine Zuordnung zu for-Schleifen, die so aussieht, als würde for(...; int i = foo(); ) ...;dies durch den Körper gehen, solange ies wahr ist, und es jedes Mal neu initialisieren. Die Schleife, die Sie zeigen, demonstriert einfach eine Variablendeklaration, aber keine Variablendeklaration, die gleichzeitig als Bedingung fungiert :)
Johannes Schaub - litb

5
Sehr gut, außer dass Sie nicht erwähnt haben, dass die beabsichtigte Verwendung dieser Funktion für dynamische Zeiger-Casts war, glaube ich.
mmocny

29

Verhindern, dass Kommaoperatoren Operatorüberladungen aufrufen

Manchmal verwenden Sie den Kommaoperator gültig, möchten aber sicherstellen, dass kein benutzerdefinierter Kommaoperator in die Quere kommt, weil Sie sich beispielsweise auf Sequenzpunkte zwischen der linken und rechten Seite verlassen oder sicherstellen möchten, dass nichts den gewünschten stört Aktion. Hier void()kommt das Spiel ins Spiel:

for(T i, j; can_continue(i, j); ++i, void(), ++j)
  do_code(i, j);

Ignoriere die Platzhalter, die ich für die Bedingung und den Code angegeben habe. Was wichtig ist void(), ist das , was den Compiler dazu zwingt, den eingebauten Kommaoperator zu verwenden. Dies kann manchmal auch bei der Implementierung von Merkmalsklassen hilfreich sein.


Ich habe das nur benutzt, um meinen ignoranten Overkill-Ausdruck zu beenden . :)
GManNickG

28

Array-Initialisierung im Konstruktor. Zum Beispiel in einer Klasse, wenn wir ein Array von intas haben:

class clName
{
  clName();
  int a[10];
};

Wir können alle Elemente im Array auf ihre Standardeinstellung (hier alle Elemente des Arrays auf Null) im Konstruktor wie folgt initialisieren:

clName::clName() : a()
{
}

6
Sie können dies mit jedem Array überall tun.
Potatoswatter

@ Potatoswatter: schwieriger als es aussieht, aufgrund der ärgerlichsten Analyse. Ich kann mir nirgendwo anders
vorstellen

Wenn der Typ des Arrays ein Klassentyp ist, wird dies nicht benötigt, oder?
Thomas Eding

27

Oooh, ich kann stattdessen eine Liste mit Tierhassen erstellen:

  • Destruktoren müssen virtuell sein, wenn Sie polymorph verwenden möchten
  • Manchmal werden Mitglieder standardmäßig initialisiert, manchmal nicht
  • Lokale Klassen können nicht als Vorlagenparameter verwendet werden (macht sie weniger nützlich).
  • Ausnahmespezifizierer: sehen nützlich aus, sind es aber nicht
  • Funktionsüberladungen verbergen Basisklassenfunktionen mit unterschiedlichen Signaturen.
  • Keine nützliche Standardisierung für die Internationalisierung (tragbarer Standard-Breitensatz, irgendjemand? Wir müssen bis C ++ 0x warten)

Auf der positiven Seite

  • versteckte Funktion: Funktionsversuchsblöcke. Leider habe ich keine Verwendung dafür gefunden. Ja, ich weiß, warum sie es hinzugefügt haben, aber Sie müssen einen Konstruktor neu einwerfen, der es sinnlos macht.
  • Es lohnt sich, die STL-Garantien für die Gültigkeit des Iterators nach der Änderung des Containers sorgfältig zu prüfen, damit Sie einige etwas schönere Schleifen erstellen können.
  • Boost - es ist kaum ein Geheimnis, aber es lohnt sich.
  • Rückgabewertoptimierung (nicht offensichtlich, aber vom Standard ausdrücklich erlaubt)
  • Functors aka Funktionsobjekte aka operator (). Dies wird von der STL ausgiebig genutzt. Nicht wirklich ein Geheimnis, aber ein raffinierter Nebeneffekt der Überlastung des Bedieners und der Vorlagen.

16
Haustierhass: Kein definierter ABI für C ++ - Apps, im Gegensatz zu C-Apps, die jeder verwendet, da jede Sprache garantieren kann, dass eine C-Funktion aufgerufen wird, kann niemand dasselbe für C ++ tun.
Gbjbaanb

8
Destruktoren müssen nur dann virtuell sein, wenn Sie polymorph zerstören möchten, was sich vom ersten Punkt geringfügig unterscheidet.
David Rodríguez - Dribeas

2
Mit C ++ 0x können lokale Typen als Vorlagenparameter verwendet werden.
Tstenner

1
Mit C ++ 0x sind Destruktoren virtuell, wenn das Objekt virtuelle Funktionen hat (dh eine vtable).
Macke

Vergessen Sie NRVO nicht, und natürlich ist jede Optimierung zulässig, solange die Programmausgabe nicht geändert wird
jk.

26

Sie können ohne undefiniertes Verhalten und mit erwarteter Semantik auf geschützte Daten und Funktionsmitglieder jeder Klasse zugreifen. Lesen Sie weiter, um zu sehen, wie. Lesen Sie dazu auch den Fehlerbericht .

Normalerweise verbietet Ihnen C ++ den Zugriff auf nicht statisch geschützte Elemente des Objekts einer Klasse, selbst wenn diese Klasse Ihre Basisklasse ist

struct A {
protected:
    int a;
};

struct B : A {
    // error: can't access protected member
    static int get(A &x) { return x.a; }
};

struct C : A { };

Das ist verboten: Sie und der Compiler wissen nicht, worauf die Referenz tatsächlich verweist. Es könnte sich um ein CObjekt handeln. In diesem Fall Bhat die Klasse kein Geschäft und keine Ahnung von ihren Daten. Ein solcher Zugriff wird nur gewährt, wenn xauf eine abgeleitete oder von dieser abgeleitete Klasse verwiesen wird. Und es könnte jedem beliebigen Code erlauben, jedes geschützte Mitglied zu lesen, indem nur eine "Wegwerf" -Klasse gebildet wird, die Mitglieder vorliest, zum Beispiel std::stack:

void f(std::stack<int> &s) {
    // now, let's decide to mess with that stack!
    struct pillager : std::stack<int> {
        static std::deque<int> &get(std::stack<int> &s) {
            // error: stack<int>::c is protected
            return s.c;
        }
    };

    // haha, now let's inspect the stack's middle elements!
    std::deque<int> &d = pillager::get(s);
}

Wie Sie sehen, würde dies sicherlich viel zu viel Schaden anrichten. Aber jetzt erlauben Mitgliedszeiger, diesen Schutz zu umgehen! Der entscheidende Punkt ist, dass der Typ eines Mitgliedszeigers an die Klasse gebunden ist, die das Mitglied tatsächlich enthält - nicht an die Klasse, die Sie bei der Übernahme der Adresse angegeben haben. Dies ermöglicht es uns, die Überprüfung zu umgehen

struct A {
protected:
    int a;
};

struct B : A {
    // valid: *can* access protected member
    static int get(A &x) { return x.*(&B::a); }
};

struct C : A { };

Und natürlich funktioniert es auch mit dem std::stackBeispiel.

void f(std::stack<int> &s) {
    // now, let's decide to mess with that stack!
    struct pillager : std::stack<int> {
        static std::deque<int> &get(std::stack<int> &s) {
            return s.*(pillager::c);
        }
    };

    // haha, now let's inspect the stack's middle elements!
    std::deque<int> &d = pillager::get(s);
}

Mit einer using-Deklaration in der abgeleiteten Klasse, die den Mitgliedsnamen öffentlich macht und auf das Mitglied der Basisklasse verweist, wird dies noch einfacher.

void f(std::stack<int> &s) {
    // now, let's decide to mess with that stack!
    struct pillager : std::stack<int> {
        using std::stack<int>::c;
    };

    // haha, now let's inspect the stack's middle elements!
    std::deque<int> &d = s.*(&pillager::c);
}


26

Eine weitere versteckte Funktion ist, dass Sie Klassenobjekte aufrufen können, die in Funktionszeiger oder Referenzen konvertiert werden können. Die Überlastungsauflösung erfolgt anhand des Ergebnisses, und die Argumente werden perfekt weitergeleitet.

template<typename Func1, typename Func2>
class callable {
  Func1 *m_f1;
  Func2 *m_f2;

public:
  callable(Func1 *f1, Func2 *f2):m_f1(f1), m_f2(f2) { }
  operator Func1*() { return m_f1; }
  operator Func2*() { return m_f2; }
};

void foo(int i) { std::cout << "foo: " << i << std::endl; }
void bar(long il) { std::cout << "bar: " << il << std::endl; }

int main() {
  callable<void(int), void(long)> c(foo, bar);
  c(42); // calls foo
  c(42L); // calls bar
}

Diese werden als "Ersatzanruffunktionen" bezeichnet.


1
Wenn Sie sagen, dass die Überlastungsauflösung für das Ergebnis erfolgt, bedeutet dies, dass es tatsächlich in beide Funktoren konvertiert wird und dann die Überlastungsauflösung erfolgt? Ich habe versucht, etwas im Operator Func1 * () und im Operator Func2 * () zu drucken, aber es scheint das richtige auszuwählen, wenn es herausfindet, welcher Konvertierungsoperator aufgerufen werden soll.
Navigator

3
@navigator, yep es konvertiert konzeptionell in beide und wählt dann das Beste aus. Es muss sie nicht wirklich aufrufen, da es vom Ergebnistyp weiß, was sie bereits ergeben werden. Der eigentliche Anruf ist beendet, wenn sich herausstellt, was schließlich ausgewählt wurde.
Johannes Schaub - litb

26

Versteckte Funktionen:

  1. Reine virtuelle Funktionen können implementiert werden. Allgemeines Beispiel: reiner virtueller Destruktor.
  2. Wenn eine Funktion eine Ausnahme auslöst, die nicht in ihren Ausnahmespezifikationen aufgeführt ist, die Funktion jedoch std::bad_exceptionin ihrer Ausnahmespezifikation enthält, wird die Ausnahme in konvertiert std::bad_exceptionund automatisch ausgelöst. Auf diese Weise wissen Sie zumindest, dass ein bad_exceptiongeworfen wurde. Lesen Sie hier mehr .

  3. Funktionsversuchsblöcke

  4. Das Template-Schlüsselwort zur Unterscheidung von Typedefs in einer Klassenvorlage. Wenn der Name eines Mitglied Template - Spezialisierung erscheint nach ., ->oder ::Operator, und dieser Name hat explizit qualifizierte Template - Parameter, Präfix das Element Template - Namen mit der Keyword - Vorlage. Lesen Sie hier mehr .

  5. Die Standardeinstellungen für Funktionsparameter können zur Laufzeit geändert werden. Lesen Sie hier mehr .

  6. A[i] funktioniert so gut wie i[A]

  7. Temporäre Instanzen einer Klasse können geändert werden! Eine nicht konstante Mitgliedsfunktion kann für ein temporäres Objekt aufgerufen werden. Beispielsweise:

    struct Bar {
      void modify() {}
    }
    int main (void) {
      Bar().modify();   /* non-const function invoked on a temporary. */
    }

    Lesen Sie hier mehr .

  8. Wenn vor und nach dem Operatorausdruck :ternary ( ?:) zwei verschiedene Typen vorhanden sind , ist der resultierende Typ des Ausdrucks der allgemeinste der beiden. Beispielsweise:

    void foo (int) {}
    void foo (double) {}
    struct X {
      X (double d = 0.0) {}
    };
    void foo (X) {} 
    
    int main(void) {
      int i = 1;
      foo(i ? 0 : 0.0); // calls foo(double)
      X x;
      foo(i ? 0.0 : x);  // calls foo(X)
    }

P Papa: A [i] == * (A + i) == * (i + A) == i [A]
abelenky

Ich verstehe die Kommutierung, es bedeutet nur, dass [] keinen eigenen semantischen Wert hat und einfach einem Ersatz im Makrostil entspricht, bei dem "x [y]" durch "(* ((x) + (y)" ersetzt wird ))) ". Überhaupt nicht das, was ich erwartet hatte. Ich frage mich, warum es so definiert ist.
P Daddy

Abwärtskompatibilität mit C
jmucchiello

2
In Bezug auf den ersten Punkt: Es gibt einen Fall , wo Sie haben eine rein virtuelle Funktion zu implementieren: rein virtuelle Destruktoren.
Frerich Raabe

24

map::operator[]Erstellt einen Eintrag, wenn der Schlüssel fehlt, und gibt den Verweis auf den standardmäßig erstellten Eintragswert zurück. So können Sie schreiben:

map<int, string> m;
string& s = m[42]; // no need for map::find()
if (s.empty()) { // assuming we never store empty values in m
  s.assign(...);
}
cout << s;

Ich bin erstaunt, wie viele C ++ - Programmierer das nicht wissen.


11
Und am anderen Ende können Sie den Operator [] nicht auf einer konstanten Karte verwenden
David Rodríguez - dribeas

2
+1 für Nick, Leute können verrückt werden, wenn sie nichts davon wissen .find().
LiraNuna

oder " const map::operator[]generiert Fehlermeldungen"
nur jemand

2
Es ist kein Merkmal der Sprache, sondern ein Merkmal der Standardvorlagenbibliothek. Es ist auch ziemlich offensichtlich, da operator [] eine gültige Referenz zurückgibt.
Ramon Zarazua B.

2
Ich musste Karten für eine Weile in C # verwenden, wo sich Karten nicht so verhalten, um zu erkennen, dass dies eine Funktion ist. Ich dachte, ich hätte mich mehr darüber geärgert, als ich es benutzt habe, aber es scheint, dass ich falsch lag. Ich vermisse es in C #.
sbi

20

Durch das Einfügen von Funktionen oder Variablen in einen namenlosen Namespace wird die Verwendung von staticeingeschränkt, um sie auf den Dateibereich zu beschränken.


"veraltet" ist ein starker Begriff ...
Potatoswatter

@Potato: Alter Kommentar, ich weiß, aber der Standard sagt, dass die Verwendung von statisch im Namespace-Bereich veraltet ist, wobei unbenannte Namespaces bevorzugt werden.
GManNickG

@ GMan: Nein, ich glaube nicht, dass SO-Seiten wirklich "sterben". Nur für beide Seiten der Geschichte wird der staticglobale Geltungsbereich in keiner Weise veraltet. (Als Referenz: C ++ 03 §D.2)
Potatoswatter

Ah, bei näherer Betrachtung: "Ein im globalen Namespace deklarierter Name hat einen globalen Namespace-Bereich (auch als globaler Bereich bezeichnet)." Bedeutet das das wirklich?
Potatoswatter

@ Potato: Ja. :) staticuse sollte nur innerhalb eines Klassentyps oder einer Funktion verwendet werden.
GManNickG

19

Das Definieren gewöhnlicher Freundfunktionen in Klassenvorlagen erfordert besondere Aufmerksamkeit:

template <typename T> 
class Creator { 
    friend void appear() {  // a new function ::appear(), but it doesn't 
                           // exist until Creator is instantiated 
    } 
};
Creator<void> miracle;  // ::appear() is created at this point 
Creator<double> oops;   // ERROR: ::appear() is created a second time! 

In diesem Beispiel erstellen zwei verschiedene Instanziierungen zwei identische Definitionen - eine direkte Verletzung des ODR

Wir müssen daher sicherstellen, dass die Vorlagenparameter der Klassenvorlage im Typ einer in dieser Vorlage definierten Friend-Funktion angezeigt werden (es sei denn, wir möchten mehr als eine Instanziierung einer Klassenvorlage in einer bestimmten Datei verhindern, dies ist jedoch eher unwahrscheinlich). Wenden wir dies auf eine Variation unseres vorherigen Beispiels an:

template <typename T> 
class Creator { 
    friend void feed(Creator<T>*){  // every T generates a different 
                                   // function ::feed() 
    } 
}; 

Creator<void> one;     // generates ::feed(Creator<void>*) 
Creator<double> two;   // generates ::feed(Creator<double>*) 

Haftungsausschluss: Ich habe diesen Abschnitt aus C ++ - Vorlagen eingefügt : The Complete Guide / Section 8.4


18

void-Funktionen können void-Werte zurückgeben

Wenig bekannt, aber der folgende Code ist in Ordnung

void f() { }
void g() { return f(); }

Aswell wie der folgende seltsam aussehende

void f() { return (void)"i'm discarded"; }

Wenn Sie dies wissen, können Sie in einigen Bereichen davon profitieren. Ein Beispiel: voidFunktionen können keinen Wert zurückgeben, aber Sie können auch nicht einfach nichts zurückgeben, da sie möglicherweise mit nicht ungültig instanziiert werden. Anstatt den Wert in einer lokalen Variablen zu speichern, die einen Fehler verursacht void, geben Sie einfach einen Wert direkt zurück

template<typename T>
struct sample {
  // assume f<T> may return void
  T dosomething() { return f<T>(); }

  // better than T t = f<T>(); /* ... */ return t; !
};

17

Lesen Sie eine Datei in einen Vektor von Zeichenfolgen:

 vector<string> V;
 copy(istream_iterator<string>(cin), istream_iterator<string>(),
     back_inserter(V));

istream_iterator


8
Oder: vector <string> V ((istream_iterator <string> (cin)), istream_iterator <string>);
OnkelBens

5
du meinst vector<string> V((istream_iterator<string>(cin)), istream_iterator<string>());- fehlende Klammern nach dem zweiten Parameter
knittl

1
Dies ist keine versteckte C ++ - Funktion. Eher eine STL-Funktion. STL! = Eine Sprache
Nick Bedford

14

Sie können Bitfelder vorlegen.

template <size_t X, size_t Y>
struct bitfield
{
    char left  : X;
    char right : Y;
};

Ich habe mir noch keinen Zweck dafür ausgedacht, aber es hat mich verdammt noch mal überrascht.


1
Siehe hier, wo ich es kürzlich für die n-Bit-Arithmetik vorgeschlagen habe: stackoverflow.com/questions/8309538/…
sehe

14

Eine der interessantesten Grammatiken aller Programmiersprachen.

Drei dieser Dinge gehören zusammen und zwei sind etwas ganz anderes ...

SomeType t = u;
SomeType t(u);
SomeType t();
SomeType t;
SomeType t(SomeType(u));

Alle außer dem dritten und fünften definieren ein SomeTypeObjekt auf dem Stapel und initialisieren es (mit uin den ersten beiden Fällen und dem Standardkonstruktor im vierten. Der dritte deklariert eine Funktion, die keine Parameter akzeptiert und a zurückgibt SomeType. Der fünfte deklariert ähnlich eine Funktion , die einen Parameter mit Werten vom Typ nimmt SomeTypebenannt u.


Gibt es einen Unterschied zwischen dem 1. und dem 2.? Ich weiß jedoch, dass es sich bei beiden um Initialisierungen handelt.
Özgür

Comptrol: Das glaube ich nicht. Beide rufen am Ende den Kopierkonstruktor auf, obwohl der erste wie der Zuweisungsoperator aussieht, ist er wirklich der Kopierkonstruktor.
Abelenky

1
Wenn u ein anderer Typ als SomeType ist, ruft der erste zuerst den Konvertierungskonstruktor und dann den Kopierkonstruktor auf, während der zweite nur den Konvertierungskonstruktor aufruft.
Eclipse

3
1. ist impliziter Aufruf des Konstruktors, 2. ist expliziter Aufruf. Sehen Sie sich den folgenden Code an, um den Unterschied festzustellen: #include <iostream> class sss {public: explizites sss (int) {std :: cout << "int" << std :: endl; }; sss (double) {std :: cout << "double" << std :: endl; }; }; int main () {sss ddd (7); // ruft den Konstruktor int auf sss xxx = 7; // ruft den Doppelkonstruktor auf return 0; }
Kirill V. Lyadvinsky

True - Die erste Zeile funktioniert nicht, wenn der Konstruktor explizit deklariert ist.
Eclipse

12

Vorwärtserklärungen loswerden:

struct global
{
     void main()
     {
           a = 1;
           b();
     }
     int a;
     void b(){}
}
singleton;

Schreiben von switch-Anweisungen mit ?: Operatoren:

string result = 
    a==0 ? "zero" :
    a==1 ? "one" :
    a==2 ? "two" :
    0;

Alles in einer einzigen Zeile erledigen:

void a();
int b();
float c = (a(),b(),1.0f);

Nullstellen von Strukturen ohne Memset:

FStruct s = {0};

Winkel- und Zeitwerte normalisieren / umbrechen:

int angle = (short)((+180+30)*65536/360) * 360/65536; //==-150

Verweisen von Referenzen:

struct ref
{
   int& r;
   ref(int& r):r(r){}
};
int b;
ref a(b);
int c;
*(int**)&a = &c;

2
FStruct s = {};ist noch kürzer.
Constantin

Im letzten Beispiel wäre es einfacher mit: a (); b (); float c = 1.0f;
Zifre

2
Diese Syntax "float c = (a (), b (), 1.0f);" ist nützlich, um die Zuweisungsoperation hervorzuheben (Zuweisung von "c"). Zuweisungsoperationen sind bei der Programmierung wichtig, da sie weniger wahrscheinlich veraltet sind, IMO. Ich weiß nicht warum, könnte etwas mit funktionaler Programmierung zu tun haben, bei der der Programmstatus jedem Frame neu zugewiesen wird. PS. Und nein, "int d = (11,22,1.0f)" ist gleich "1". Vor einer Minute mit VS2008 getestet.
AareP

2
+1 Solltest du nicht anrufen main ? Ich würde vorschlagen global().main();und einfach den Singleton vergessen ( Sie können einfach mit dem temporären arbeiten, wodurch die Lebensdauer verlängert wird )
siehe

1
Ich bezweifle, dass das Zuweisen von Referenzen portabel ist. Ich liebe die Struktur, auf Vorwärtserklärungen zu verzichten.
Thomas Eding

12

Der ternäre bedingte Operator ?:verlangt, dass sein zweiter und dritter Operand "akzeptable" Typen haben (informell sprechen). Diese Anforderung hat jedoch eine Ausnahme (Wortspiel beabsichtigt): Entweder der zweite oder der dritte Operand kann ein Wurfausdruck sein (der Typ hatvoid ), unabhängig vom Typ des anderen Operanden.

Mit anderen Worten, man kann die folgenden genau gültigen C ++ - Ausdrücke mit dem ?:Operator schreiben

i = a > b ? a : throw something();

Übrigens ist die Tatsache, dass throw expression tatsächlich ein Ausdruck (vom Typ void) und keine Anweisung ist, ein weiteres wenig bekanntes Merkmal der C ++ - Sprache. Dies bedeutet unter anderem, dass der folgende Code vollkommen gültig ist

void foo()
{
  return throw something();
}

obwohl es nicht viel Sinn macht, es auf diese Weise zu tun (vielleicht ist dies in einem generischen Vorlagencode nützlich).


Für das, was es wert ist, hat Neil eine Frage dazu: stackoverflow.com/questions/1212978/… , nur für zusätzliche Informationen.
GManNickG

12

Die Dominanzregel ist nützlich, aber wenig bekannt. Selbst wenn sich die Mitglieder in einem nicht eindeutigen Pfad durch ein Gitter der Basisklasse befinden, ist die Namenssuche für ein teilweise verstecktes Mitglied eindeutig, wenn das Mitglied zu einer virtuellen Basisklasse gehört:

struct A { void f() { } };

struct B : virtual A { void f() { cout << "B!"; } };
struct C : virtual A { };

// name-lookup sees B::f and A::f, but B::f dominates over A::f !
struct D : B, C { void g() { f(); } };

Ich habe dies verwendet, um eine Ausrichtungsunterstützung zu implementieren , die mithilfe der Dominanzregel automatisch die strengste Ausrichtung ermittelt.

Dies gilt nicht nur für virtuelle Funktionen, sondern auch für typedef Namen, statische / nicht virtuelle Mitglieder und alles andere. Ich habe gesehen, dass es verwendet wurde, um überschreibbare Merkmale in Metaprogrammen zu implementieren.


1
Ordentlich. Gibt es einen bestimmten Grund, den Sie struct Cin Ihr Beispiel aufgenommen haben ...? Prost.
Tony Delroy
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.