it-swarm-eu.dev

Versteckte Funktionen von C ++?

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

114
Craig H

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 die Abkürzung für

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

Mit Vorsicht verwenden :-)

308
Ferruccio

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

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

    ...
}
238
Ben

Zeigerarithmetik.

C++ - Programmierer bevorzugen es, Zeiger wegen der Fehler, die eingeführt werden können, zu vermeiden.

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

140
Anonymouse

Ich stimme den meisten Posts dort zu: C++ ist eine Multiparadigmasprache, daher sind die "versteckten" Funktionen, die Sie finden (abgesehen von "undefinierten Verhaltensweisen", die Sie unbedingt vermeiden sollten), clevere Funktionen.

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

Das wichtigste ist das [~ # ~] raii [~ # ~] , das oft jahrelang von C++ - Entwicklern aus der C-Welt ignoriert wird. Das Überladen von Operatoren ist häufig eine missverstandene Funktion, die sowohl Array-ähnliches Verhalten (tiefgestellter Operator) als auch Zeiger-ähnliche Operationen (intelligente Zeiger) und eingebaute Funktionen ermöglicht Operationen (Multiplikation von Matrizen.

Die Verwendung der Ausnahmebedingung ist oft schwierig, kann aber mit einigem Aufwand durch die Ausnahmesicherheit wirklich robusten Code erzeugen Spezifikationen (einschließlich Code, der nicht fehlschlägt oder über eine festschreibungsähnliche Funktion verfügt, die erfolgreich ist oder zum ursprünglichen Zustand zurückkehrt).

Die bekannteste "versteckte" Funktion von C++ ist die Template-Metaprogrammierung , mit der Sie Ihr Programm zum Zeitpunkt der Kompilierung teilweise (oder vollständig) ausführen können statt Laufzeit. Dies ist jedoch schwierig, und Sie müssen ein solides Verständnis für Vorlagen haben, bevor Sie es versuchen.

Andere machen Gebrauch von dem multiplen Paradigma, um "Wege der Programmierung" außerhalb von C++ 's Vorfahren zu erzeugen, das heißt, C.

Mit Funktoren können Sie Funktionen simulieren, die zusätzlich typensicher und zustandsbehaftet sind. 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 erzeugen, 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 für die, an die Sie zuerst gedacht haben. Sie können auch die Typensicherheit erhöhen (wie ein automatisiertes typensicheres malloc/realloc/free). C++ - Objektfunktionen sind sehr mächtig (und daher gefährlich, wenn sie unachtsam verwendet werden), aber selbst der dynamische Polymorphismus hat seine statische Version in C++: der [~ # ~] crtp [~ # ~] .

Ich habe festgestellt, dass die meisten " Effective C++" - Bücher von Scott Meyers oder " Exceptional C++" - Bücher von Herb Sutter einfach zu lesen sind und unzählige Informationen zu bekannten und weniger bekannten Funktionen von C++.

Unter meinen bevorzugten ist eine, die die Haare eines jeden Java) Programmierers aus dem Horror aufstehen lässt: In C++ die objektorientierteste Art, ein Feature hinzuzufügen Ein Objekt wird durch eine Nicht-Mitglied-Nicht-Freund-Funktion anstelle einer Mitglied-Funktion (dh Klassenmethode) erstellt, weil:

  • In C++ besteht die Schnittstelle einer Klasse aus ihren Mitgliedsfunktionen und den Nichtmitgliedsfunktionen im selben Namespace

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

Dies überrascht auch erfahrene Entwickler immer wieder aufs Neue.

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

119
paercebal

Ein Sprachfeature, 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, als ich Beispiele in der Boost-Dokumentation fand. 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 );
118
Jason Mock

Im init-Teil einer for -Schleife 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.

102

Der Array-Operator ist assoziativ.

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

Du sagtest nicht nützlich ... :-)

77
Colin Jensen

Wenig bekannt 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 Member-Funktionen haben. Nur nichts, was mit Vererbung zu tun hat (einschließlich virtueller Funktionen).

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

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

72
Konrad Rudolph

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

Umwandlung einer Aufzählung in eine Ganzzahl

+AnEnumeratorValue

Und Ihr Enumerator-Wert, der zuvor einen Aufzählungstyp hatte, hat jetzt den perfekten Integer-Typ, 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 Enumeration implementieren möchten.

Holen Sie sich den Wert aus einer Variablen

Sie müssen eine Klasse verwenden, die einen klasseninternen statischen Initialisierer ohne eine Definition außerhalb der Klasse verwendet, die jedoch manchmal nicht verknüpft werden kann. Der Bediener kann dabei helfen, ein temporäres Objekt zu erstellen, ohne Annahmen oder Abhängigkeiten von seinem Typ zu machen

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 zu einem Zeiger

Möchten Sie zwei Zeiger an eine Funktion übergeben, die jedoch nicht funktioniert? 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
}

Die Lebensdauer von Temporären, die an konstante Referenzen gebunden sind, ist eine, die nur wenige Menschen kennen. Zumindest ist es mein Lieblingsstück in C++, von dem die meisten Leute nichts wissen.

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

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 besteht darin, eine Ausnahme in eine andere Ausnahmeklasse zu übersetzen und erneut zu werfen oder zwischen Ausnahmen und rückgabebasierter Fehlercodebehandlung zu übersetzen.

52
vividos

Viele kennen die Metafunktion identity/id, aber es gibt eine gute Verwendungsmöglichkeit für Fälle ohne Vorlage:

// 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 beim Entschlüsseln von C++ - Deklarationen!

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

Eine ziemlich versteckte Funktion ist, dass Sie Variablen innerhalb einer if-Bedingung definieren können, und ihr Umfang erstreckt sich nur über den if- und den else-Block:

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

Einige Makros verwenden dies beispielsweise, um 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 nutzt es unter der Haube. Um dies zu vervollständigen, ist es nicht nur in einem If, sondern auch in einem Switch möglich:

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

und in einer while-Schleife:

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

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

Verhindern, dass Kommaoperatoren Überladungen von Operatoren aufrufen

Manchmal verwenden Sie den Komma-Operator richtig, aber Sie möchten sicherstellen, dass kein benutzerdefinierter Komma-Operator in die Quere kommt, weil Sie sich beispielsweise auf Sequenzpunkte zwischen der linken und rechten Seite verlassen oder sicherstellen möchten, dass nichts das Gewünschte beeinträchtigt Aktion. Hier kommt void() ins Spiel:

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

Ignorieren Sie die Platzhalter, die ich für die Bedingung und den Code angegeben habe. Wichtig ist die Funktion void(), mit der der Compiler die Verwendung des eingebauten Komma-Operators erzwingt. Dies kann manchmal auch beim Implementieren von Merkmalsklassen hilfreich sein.

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

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

Wir können alle Elemente im Array auf den Standardwert (hier alle Elemente des Arrays auf Null) im Konstruktor initialisieren als:

clName::clName() : a()
{
}
28
Poorna

Oooh, ich kann mir stattdessen eine Liste mit Tierhassen einfallen lassen:

  • Destruktoren müssen virtuell sein, wenn Sie 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 sinnvolle Standardisierung zur Internationalisierung (portabler standardweiter Zeichensatz? Wir müssen warten bis C++ 0x)

Auf der positiven Seite

  • versteckte Eigenschaft: 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 erneut einsetzen, der es sinnlos macht.
  • Es lohnt sich, die STL-Garantien für die Gültigkeit von Iteratoren nach der Container-Modifikation zu beachten, mit denen Sie einige etwas schönere Schleifen erstellen können.
  • Boost - es ist kein Geheimnis, aber es lohnt sich.
  • Rückgabewertoptimierung (nicht offensichtlich, aber vom Standard ausdrücklich erlaubt)
  • Funktoren oder Funktionsobjekte oder Operator (). Dies wird von der AWL ausgiebig genutzt. Nicht wirklich ein Geheimnis, aber ein raffinierter Nebeneffekt der Überladung von Operatoren und Vorlagen.
27
Robert

Sie können ohne undefiniertes Verhalten und mit der erwarteten Semantik auf geschützte Daten und Funktionsmember einer beliebigen Klasse zugreifen. Lesen Sie weiter, um zu sehen, wie. Lesen Sie dazu auch die Mängelanzeige .

Normalerweise verbietet C++ den Zugriff auf nicht statisch geschützte Elemente eines Klassenobjekts, 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 C Objekt handeln. In diesem Fall hat die Klasse B keine Ahnung von ihren Daten. Ein solcher Zugriff wird nur gewährt, wenn x eine Referenz auf eine abgeleitete Klasse oder eine davon abgeleitete Klasse ist. Und es könnte jedem beliebigen Teil des Codes erlauben, ein geschütztes Mitglied zu lesen, indem einfach eine "Wegwerf" -Klasse erstellt wird, die Mitglieder ausliest, zum Beispiel von 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 es Mitgliedszeiger, diesen Schutz zu umgehen! Der entscheidende Punkt ist, dass der Typ eines Elementzeigers an die Klasse gebunden ist, die das Element tatsächlich enthält - und nicht an die Klasse, die Sie beim Aufnehmen des Elements angegeben haben Adresse. 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::stack Beispiel.

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

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_exception In ihrer Ausnahmespezifikation enthält, wird die Ausnahme in std::bad_exception Konvertiert und automatisch ausgelöst. Auf diese Weise werden Sie zumindest wissen, dass ein bad_exception Geworfen wurde. Lesen Sie mehr hier .

  3. funktionsversuchsblöcke

  4. Das Schlüsselwort template zur eindeutigen Unterscheidung von Typedefs in einer Klassenvorlage. Wenn der Name einer Mitgliedervorlagenspezialisierung nach einem Operator ., -> Oder :: Angezeigt wird und dieser Name explizit qualifizierte Vorlagenparameter aufweist, setzen Sie das Präfix vor den Namen der Mitgliedervorlage Keyword-Vorlage. Lesen Sie mehr hier .

  5. die Standardeinstellungen der Funktionsparameter können zur Laufzeit geändert werden. Lesen Sie mehr hier .

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

  7. Temporäre Instanzen einer Klasse können geändert werden! Eine Nicht-Konstanten-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 mehr hier .

  8. Wenn zwei verschiedene Typen vor und nach dem : Im ternären Operatorausdruck (?:) 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)
    }
    
26
Sumant

Eine weitere versteckte Funktion besteht darin, dass Sie Klassenobjekte aufrufen können, die in Funktionszeiger oder Referenzen konvertiert werden können. Überladungsauflösung wird auf dem Ergebnis von ihnen durchgeführt, und 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 "Ersatzaufruffunktionen" genannt.

map::operator[] erstellt einen Eintrag, wenn der Schlüssel fehlt, und gibt einen 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.

24
Constantin

Das Einfügen von Funktionen oder Variablen in einen namenlosen Namespace setzt die Verwendung von static außer Kraft, um sie auf den Dateibereich zu beschränken.

20
Jim Hunziker

Das Definieren von normalen Friend-Funktionen 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 erscheinen (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++ Templates: The Complete Guide /Abschnitt 8.4 eingefügt

19
Özgür

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

Wenig bekannt, aber der folgende Code ist in Ordnung

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

Aswell wie die folgende seltsam aussehende

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

Wenn Sie dies wissen, können Sie in einigen Bereichen davon profitieren. Ein Beispiel: void -Funktionen können keinen Wert zurückgeben, aber Sie können auch nicht einfach nichts zurückgeben, da sie möglicherweise mit non-void instanziiert werden. Anstatt den Wert in einer lokalen Variablen zu speichern, die einen Fehler für void verursacht, 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; !
};

Lies eine Datei in einen Vektor von Strings:

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

istream_iterator

17
Jason Baker

Eine der interessantesten Grammatiken aller Programmiersprachen.

Drei davon 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 SomeType -Objekt auf dem Stapel und initialisieren es (mit u in den ersten beiden Fällen und dem Standardkonstruktor in der vierten. Der dritte deklariert eine Funktion, die Nimmt keine Parameter und gibt ein SomeType zurück. Das fünfte deklariert auf ähnliche Weise eine Funktion, die einen Parameter nach dem Wert vom Typ SomeType mit dem Namen u annimmt.

14
Eclipse

Sie können Bitfelder als Vorlage verwenden.

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

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

14
Kaz Dragon

Die Dominanzregel ist nützlich, aber wenig bekannt. Es heißt, dass die Namenssuche für ein teilweise ausgeblendetes Mitglied auch dann eindeutig ist, wenn es sich in einem nicht eindeutigen Pfad durch ein Basisklassengitter befindet, 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 Alignment-Unterstützung implementieren das automatisch die strengste Ausrichtung mithilfe der Dominanzregel ermittelt.

Dies gilt nicht nur für virtuelle Funktionen, sondern auch für typedef-Namen, statische/nicht-virtuelle Mitglieder und alles andere. Ich habe es früher gesehen überschreibbare Merkmale in Metaprogrammen implementieren.

Der ternäre bedingte Operator ?: setzt voraus, dass sein zweiter und dritter Operand "akzeptable" Typen haben (informell gesprochen). Diese Anforderung hat jedoch eine Ausnahme (Wortspiel beabsichtigt): Entweder der zweite oder der dritte Operand kann ein Auslösungsausdruck sein (der den Typ void hat), unabhängig vom Typ des anderen Operanden.

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

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 sinnvoll ist, dies auf diese Weise zu tun (möglicherweise ist dies in einem generischen Vorlagencode nützlich).

12
AnT

Forward-Deklarationen loswerden:

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

Switch-Anweisungen schreiben mit?: Operator:

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

Alles in einer Zeile machen:

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

Nullstellen von Strukturen ohne Memset:

FStruct s = {0};

Winkel- und Zeitwerte normalisieren/wickeln:

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

Referenzen zuweisen:

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

Ich fand diesen Blog eine erstaunliche Ressource über die Arkane von C++: C++ Truths .

9
Drealmer

Ein gefährliches Geheimnis ist

Fred* f = new(ram) Fred(); http://www.parashift.com/c++-faq-lite/dtors.html#faq-11.10
f->~Fred();

Mein Lieblingsgeheimnis sehe ich selten gebraucht:

class A
{
};

struct B
{
  A a;
  operator A&() { return a; }
};

void func(A a) { }

int main()
{
  A a, c;
  B b;
  a=c;
  func(b); //yeah baby
  a=b; //gotta love this
}
8
user34537

Lokale Klassen sind fantastisch:

struct MyAwesomeAbstractClass
{ ... };


template <typename T>
MyAwesomeAbstractClass*
create_awesome(T param)
{
    struct ans : MyAwesomeAbstractClass
    {
        // Make the implementation depend on T
    };

    return new ans(...);
}

ziemlich ordentlich, da es den Namespace nicht mit nutzlosen Klassendefinitionen verschmutzt ...

8
Alexandre C.

Eine versteckte Funktion, auch für die GCC-Entwickler , besteht darin, ein Array-Mitglied mit einem String-Literal zu initialisieren. Angenommen, Sie haben eine Struktur, die mit einem C-Array arbeiten muss, und Sie möchten das Array-Mitglied mit einem Standardinhalt initialisieren

struct Person {
  char name[255];
  Person():name("???") { }
};

Dies funktioniert und funktioniert nur mit char-Arrays und String-Literal-Initialisierern. Es wird kein strcpy benötigt!

Ein Beispiel von vielen: Template-Metaprogrammierung. Niemand im Normungsausschuss wollte eine Turing-vollständige Subsprache, die zur Kompilierungszeit ausgeführt wird.

Template-Metaprogrammierung ist kaum ein verstecktes Merkmal. Es ist sogar in der Boost-Bibliothek. Siehe MPL . Aber wenn "fast versteckt" gut genug ist, dann werfen Sie einen Blick auf die Boost-Bibliotheken . Es enthält viele Goodies, die ohne die Unterstützung einer starken Bibliothek nicht leicht zugänglich sind.

Ein Beispiel ist boost.lambda library, was interessant ist, da C++ im aktuellen Standard keine Lambda-Funktionen hat.

Ein weiteres Beispiel ist Loki , das "die Metaprogrammierung von C++ - Vorlagen in großem Umfang nutzt und mehrere häufig verwendete Tools implementiert: Typelist, Functor, Singleton, Smart Pointer, Object Factory, Visitor und Multimethods". [ Wikipedia ]

6
Markowitch

Es gibt keine versteckten Funktionen, aber die Sprache C++ ist sehr mächtig und häufig konnten sich selbst Entwickler von Standard nicht vorstellen, wofür C++ verwendet werden kann.

Eigentlich kann man aus einer einfachen Sprachkonstruktion etwas sehr Mächtiges schreiben. Viele solcher Dinge sind unter www.boost.org als Beispiele verfügbar (und http://www.boost.org/doc/libs/1_36_0/doc/html/lambda.html darunter ).

Um zu verstehen, wie einfache Sprachkonstruktionen zu etwas Mächtigem kombiniert werden können, ist es gut zu lesen "C++ - Vorlagen: Der vollständige Leitfaden" von David Vandevoorde, Nicolai M. Josuttis und wirklich magisches Buch "Modernes C++ Design ..." von Andrei Alexandresc .

Und schließlich ist es schwierig, C++ zu lernen, Sie sollten versuchen, es zu füllen;)

5
sergtk

Es scheint mir, dass nur wenige Leute über unbenannte Namespaces Bescheid wissen:

namespace {
  // Classes, functions, and objects here.
}

Unbenannte Namespaces verhalten sich so, als ob sie ersetzt würden durch:

namespace __unique_{ /* empty body */ }
using namespace __unique_name__;
namespace __unique_{
  // original namespace body
}

"..wobei alle Vorkommen von [diesem eindeutigen Namen] in einer Übersetzungseinheit durch denselben Bezeichner ersetzt werden und dieser Bezeichner sich von allen anderen Bezeichnern im gesamten Programm unterscheidet." [C++ 03, 7.3.1.1/1]

4
vobject
4
Özgür

Ich bin mir nicht sicher, was das Versteckte angeht, aber es gibt einige interessante'Tricks' , die beim Lesen der Spezifikation wahrscheinlich nicht offensichtlich sind.

3
dbrien

Es gibt viele "undefinierte Verhaltensweisen". Sie können lernen, wie Sie vermeiden, dass sie gute Bücher lesen und die Standards lesen.

3
ugasoft

Die meisten C++ - Entwickler ignorieren die Möglichkeiten der Template-Metaprogrammierung. Check out Loki Libary . Es implementiert mehrere erweiterte Tools wie Typelist, Functor, Singleton, Smart Pointer, Object Factory, Visitor und Multimethods, wobei die Template-Metaprogrammierung umfassend genutzt wird (von wikipedia ). Zum größten Teil könnten Sie diese als "versteckte" c ++ - Funktion betrachten.

3
Sridhar Iyer

From C++ Truths .

Das Definieren von Funktionen mit identischen Signaturen in demselben Bereich ist daher zulässig:

template<class T> // (a) a base template
void f(T) {
  std::cout << "f(T)\n";
}

template<>
void f<>(int*) { // (b) an explicit specialization
  std::cout << "f(int *) specilization\n";
}

template<class T> // (c) another, overloads (a)
void f(T*) {
  std::cout << "f(T *)\n";
}

template<>
void f<>(int*) { // (d) another identical explicit specialization
  std::cout << "f(int *) another specilization\n";
}
3
Özgür
  • zeiger auf Klassenmethoden
  • Das Schlüsselwort "typename"
3
shoosh
3
sdcvvc

main () benötigt keinen Rückgabewert:

int main(){}

ist das kürzeste gültige C++ - Programm.

2
Jeffrey Faust

Achten Sie auf den Unterschied zwischen den Initialisierungen des Zeigers für freie Funktionen und des Zeigers für Elementfunktionen:

mitgliedsfunktion:

struct S
{
 void func(){};
};
int main(){
void (S::*pmf)()=&S::func;//  & is mandatory
}

und freie Funktion:

void func(int){}
int main(){
void (*pf)(int)=func; // & is unnecessary it can be &func as well; 
}

Dank dieses redundanten & können Sie Stream-Manipulatoren, die freie Funktionen sind, in der Kette hinzufügen:

cout<<hex<<56; //otherwise you would have to write cout<<&hex<<56, not neat.
2
Özgür
  1. map::insert(std::pair(key, value)); überschreibt nicht, wenn der Schlüsselwert bereits vorhanden ist.

  2. Sie können eine Klasse direkt nach ihrer Definition instanziieren: (Ich könnte hinzufügen, dass diese Funktion mir aufgrund des fehlenden Semikolons Hunderte von Kompilierungsfehlern beschert hat, und ich habe noch nie jemanden gesehen, der dies für Klassen verwendet hat.)

    class MyClass {public: /* code */} myClass;
    
2
Viktor Sehr

Es gibt Unmengen von "kniffligen" Konstrukten in C++. Sie gehen von "einfachen" Implementierungen von Sealed/Final Classes mit virtueller Vererbung aus. Und kommen Sie zu ziemlich "komplexen" Meta-Programmierkonstrukten wie Boosts MPL ( Tutorial ). Die Möglichkeiten, sich selbst in den Fuß zu schießen, sind endlos. Wenn Sie jedoch in Schach gehalten werden (d. H. Erfahrene Programmierer), bieten Sie einige der besten Flexibilität in Bezug auf Wartbarkeit und Leistung.

1
Amir

Die Klassenschlüssel class und struct sind nahezu identisch. Der Hauptunterschied besteht darin, dass Klassen standardmäßig den privaten Zugriff für Mitglieder und Basen verwenden, während Strukturen standardmäßig den öffentlichen Zugriff verwenden:

// this is completely valid C++:
class A;
struct A { virtual ~A() = 0; };
class B : public A { public: virtual ~B(); };

// means the exact same as:
struct A;
class A { public: virtual ~A() = 0; };
struct B : A { virtual ~B(); };

// you can't even tell the difference from other code whether 'struct'
// or 'class' was used for A and B

Gewerkschaften können auch Mitglieder und Methoden haben und standardmäßig öffentlich zugänglich sein, ähnlich wie Strukturen.

1
a_m0d

Indirekte Konvertierungssprache :

Angenommen, Sie entwerfen eine Smart Pointer-Klasse. Zusätzlich zur Überladung der Operatoren * und -> definiert eine Smart Pointer-Klasse normalerweise einen Konvertierungsoperator in bool:

template <class T>
class Ptr
{
public:
 operator bool() const
 {
  return (rawptr ? true: false);
 }
//..more stuff
private:
 T * rawptr;
};

Die Konvertierung in bool ermöglicht es Clients, intelligente Zeiger in Ausdrücken zu verwenden, die bool-Operanden erfordern:

Ptr<int> ptr(new int);
if(ptr ) //calls operator bool()
 cout<<"int value is: "<<*ptr <<endl;
else
 cout<<"empty"<<endl;

Darüber hinaus ist die implizite Konvertierung in bool in bedingten Deklarationen wie den folgenden erforderlich:

if (shared_ptr<X> px = dynamic_pointer_cast<X>(py))
{
 //we get here only of px isn't empty
} 

Leider öffnet diese automatische Konvertierung das Tor zu unerwünschten Überraschungen:

Ptr <int> p1;
Ptr <double> p2;

//surprise #1
cout<<"p1 + p2 = "<< p1+p2 <<endl; 
//prints 0, 1, or 2, although there isn't an overloaded operator+()

Ptr <File> pf;
Ptr <Query> pq; // Query and File are unrelated 

//surprise #2
if(pf==pq) //compares bool values, not pointers! 

Lösung: Verwenden Sie das Idiom "indirekte Konvertierung", indem Sie von Zeiger auf Datenelement [pMember] nach bool konvertieren, sodass nur eine implizite Konvertierung erfolgt, die das oben erwähnte unerwartete Verhalten verhindert: pMember-> bool statt bool-> something sonst.

1
Özgür

Wenn der Operator delete () zusätzlich zu * void ein Größenargument verwendet, bedeutet dies, dass es sich in hohem Maße um eine Basisklasse handelt. Dieses Größenargument ermöglicht es, die Größe der Typen zu überprüfen, um die richtige zu zerstören. Hier was Stephen Dewhurst darüber erzählt:

Beachten Sie auch, dass wir eine Version des Operators delete mit zwei Argumenten anstelle der üblichen Version mit einem Argument verwendet haben. Diese aus zwei Argumenten bestehende Version ist eine weitere "übliche" Version des Löschens von Mitgliedsoperatoren, die häufig von Basisklassen verwendet wird, die davon ausgehen, dass abgeleitete Klassen ihre Implementierung zum Löschen von Operatoren erben. Das zweite Argument enthält die Größe des zu löschenden Objekts - Informationen, die bei der Implementierung der benutzerdefinierten Speicherverwaltung häufig hilfreich sind.

1
Özgür

Ich finde rekursive Template-Instatiationen ziemlich cool:

template<class int>
class foo;

template
class foo<0> {
    int* get<0>() { return array; }
    int* array;  
};

template<class int>
class foo<i> : public foo<i-1> {
    int* get<i>() { return array + 1; }  
};

Ich habe das verwendet, um eine Klasse mit 10-15 Funktionen zu generieren, die Zeiger in verschiedene Teile eines Arrays zurückgeben, da für eine von mir verwendete API ein Funktionszeiger für jeden Wert erforderlich ist.

Das heißt Programmierung des Compilers zur Generierung einer Reihe von Funktionen mittels Rekursion. Einfach wie Torte. :)

1
Macke

Mein Favorit (vorerst) ist der Mangel an Sematik in einer Aussage wie A = B = C. Was der Wert von A ist grundsätzlich unbestimmt.

Denken Sie daran:

class clC
{
public:
   clC& operator=(const clC& other)
   {
      //do some assignment stuff
      return copy(other);
   }
   virtual clC& copy(const clC& other);
}

class clB : public clC
{
public:
  clB() : m_copy()
  {
  }

  clC& copy(const clC& other)
  {
    return m_copy;
  }

private:
  class clInnerB : public clC
  {
  }
  clInnerB m_copy;
}

jetzt könnte A von einem Typ sein, auf den kein anderer als Objekte des Typs clB zugreifen kann, und einen Wert haben, der nichts mit C zu tun hat.

0
Rune FS

Hinzufügen von Einschränkungen zu Vorlagen.

0
Özgür

Mitgliedszeiger und Mitgliedszeigeroperator -> *

#include <stdio.h>
struct A { int d; int e() { return d; } };
int main() {
    A* a = new A();
    a->d = 8;
    printf("%d %d\n", a ->* &A::d, (a ->* &A::e)() );
    return 0;
}

Für Methoden (a -> * & A :: e) () ist ein bisschen wie Function.call () von Javascript

var f = A.e
f.call(a) 

Für Mitglieder ist es ein bisschen wie das Zugreifen mit dem Operator []

a['d']
0
Kamil Szot

Mit einigen Compilern können Sie alle vordefinierten Makros über Befehlszeilenoptionen anzeigen. Dies funktioniert mit gcc und icc (Intels C++ - Compiler):

$ touch empty.cpp
$ g++ -E -dM empty.cpp | sort >gxx-macros.txt
$ icc -E -dM empty.cpp | sort >icx-macros.txt
$ touch empty.c
$ gcc -E -dM empty.c | sort >gcc-macros.txt
$ icc -E -dM empty.c | sort >icc-macros.txt

Bei MSVC werden sie an einem einzelnen Ort aufgelistet. Sie könnten auch für die anderen an einer einzigen Stelle dokumentiert werden, aber mit den obigen Befehlen können Sie klar sehen , was definiert ist und was nicht Die Werte werden verwendet, nachdem alle anderen Befehlszeilenoptionen angewendet wurden.

Vergleichen (nach dem Sortieren):

 $ diff gxx-macros.txt icx-macros.txt
 $ diff gxx-macros.txt gcc-macros.txt
 $ diff icx-macros.txt icc-macros.txt
0
Roger Pate
class Empty {};

namespace std {
  // #1 specializing from std namespace is okay under certain circumstances
  template<>
  void swap<Empty>(Empty&, Empty&) {} 
}

/* #2 The following function has no arguments. 
   There is no 'unknown argument list' as we do
   in C.
*/
void my_function() { 
  cout << "whoa! an error\n"; // #3 using can be scoped, as it is in main below
  // and this doesn't affect things outside of that scope
}

int main() {
  using namespace std; /* #4 you can use using in function scopes */
  cout << sizeof(Empty) << "\n"; /* #5 sizeof(Empty) is never 0 */
  /* #6 falling off of main without an explicit return means "return 0;" */
}
0
dirkgently