Ukryte funkcje C ++? [Zamknięte]


114

Żadnej miłości do C ++, jeśli chodzi o linię pytań „ukrytych funkcji”? Pomyślałem, że to tam wyrzucę. Jakie są ukryte funkcje C ++?


@Devtron - Widziałem kilka niesamowitych błędów (tj. Nieoczekiwanego zachowania) sprzedawanych jako funkcje. W rzeczywistości przemysł gier obecnie stara się to urzeczywistnić i nazywa to „grą wschodzącą” (sprawdź też „TK Surfing” z Psi-Ops, był to po prostu błąd, potem zostawili go tak, jak jest i jest to jeden z najlepsze cechy gry IMHO)
Grant Peters

5
@Laith J: Niewiele osób przeczytało 786-stronicowy standard ISO C ++ od deski do deski - ale przypuszczam, że masz i zachowałeś to wszystko, prawda?
j_random_hacker

2
@Laith, @j_random: Zobacz moje pytanie „Co to jest żart programisty, jak go rozpoznać i jaka jest właściwa odpowiedź” na stackoverflow.com/questions/1/you-have-been-link-rolled .

Odpowiedzi:


308

Większość programistów C ++ zna operator trójskładnikowy:

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

Jednak nie zdają sobie sprawy, że można go użyć jako lwartości:

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

co jest skrótem dla

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

Używaj ostrożnie :-)


11
Bardzo interesujące. Widzę, że tworzy nieczytelny kod.
Jason Baker

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

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

12
Nie wiem, czy to GCC konkretne, ale byłem zaskoczony, aby znaleźć to pracował również: (value ? function1 : function2)().
Chris Burt-Brown

3
@Chris Burt-Brown: Nie, to powinno działać wszędzie, jeśli mają ten sam typ (tj. Bez domyślnych argumentów) function1i function2są niejawnie konwertowane na wskaźniki funkcji, a wynik jest niejawnie konwertowany z powrotem.
MSalters

238

Możesz umieścić URI w źródle C ++ bez błędów. Na przykład:

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

    ...
}

41
Ale podejrzewam, że tylko jedna na funkcję? :)
Constantin,

51
@jpoh: http, po którym następuje dwukropek, staje się „etykietą”, której później użyjesz w instrukcji goto. otrzymujesz to ostrzeżenie od swojego kompilatora, ponieważ nie jest ono używane w żadnej instrukcji goto w powyższym przykładzie.
utku_karatas

9
Możesz dodać więcej niż jeden, o ile mają różne protokoły! ftp.microsoft.com gopher: //aerv.nl/1 i tak dalej ...
Daniel Earwicker,

4
@Pavel: identyfikator, po którym następuje dwukropek, jest etykietą (do użytku z goto którą ma C ++). Cokolwiek po dwóch ukośnikach jest komentarzem. Dlatego z http://stackoverflow.com, httpjest etykietą (teoretycznie można by to napisać goto http;) i //stackoverflow.comjest to tylko komentarz na końcu linii. Oba są legalnym C ++, więc konstrukcja jest kompilowana. Oczywiście nie robi to niczego pożytecznego.
David Thornley

8
Niestety goto http; rzeczywistości nie podąża za adresem URL. :(
Yakov Galka

140

Arytmetyka wskaźników.

Programiści C ++ wolą unikać wskaźników z powodu błędów, które można wprowadzić.

Jednak najfajniejszy C ++, jaki kiedykolwiek widziałem? Literały analogowe.


11
Unikamy wskazówek z powodu błędów? Wskaźniki to w zasadzie wszystko, o co chodzi w dynamicznym kodowaniu w C ++!
Nick Bedford,

1
Literały analogowe świetnie nadają się do zaciemnionych zgłoszeń konkursowych w C ++, szczególnie w przypadku grafiki ASCII.
Synetech

119

Zgadzam się z większością tamtejszych postów: C ++ jest językiem wieloparadygmatycznym, więc „ukryte” funkcje, które znajdziesz (inne niż „niezdefiniowane zachowania”, których powinieneś unikać za wszelką cenę) to sprytne wykorzystanie udogodnień.

Większość z tych udogodnień nie jest wbudowanymi funkcjami języka, ale są oparte na bibliotekach.

Najważniejszy jest RAII , często ignorowany przez lata przez programistów C ++ pochodzących ze świata C. Przeciążanie operatorów jest często niezrozumianą funkcją, która umożliwia zarówno zachowanie tablicowe (operator indeksu dolnego), operacje podobne do wskaźnika (inteligentne wskaźniki), jak i operacje podobne do wbudowanych (mnożenie macierzy).

Zastosowanie wyjątku jest często trudne, ale przy odrobinie pracy może stworzyć naprawdę solidny kod dzięki zabezpieczeniu wyjątków specyfikacjom (w tym kod, który nie zawiedzie lub będzie miał funkcje podobne do zatwierdzania, które się powiodą lub powrócą do jego pierwotny stan).

Najbardziej znaną „ukrytą” funkcją C ++ jest metaprogramowanie szablonów , ponieważ pozwala ono na częściowe (lub całkowite) wykonanie programu w czasie kompilacji zamiast w czasie wykonywania. Jest to jednak trudne i zanim spróbujesz, musisz dobrze opanować szablony.

Inne wykorzystują paradygmat wielu do tworzenia „sposobów programowania” spoza przodka C ++, to znaczy C.

Używając funktorów , można symulować funkcje, z dodatkowym zabezpieczeniem typu i stanem. Używając wzorca polecenia , możesz opóźnić wykonanie kodu. Większość innych wzorców projektowych można łatwo i wydajnie zaimplementować w C ++ w celu stworzenia alternatywnych stylów kodowania, których nie powinno się znajdować na liście „oficjalnych paradygmatów C ++”.

Korzystając z szablonów , możesz stworzyć kod, który będzie działał na większości typów, w tym na innym niż ten, o którym myślałeś na początku. Możesz także zwiększyć bezpieczeństwo typów (np. Zautomatyzowany malloc / realloc / free). Funkcje obiektu C ++ są naprawdę potężne (a zatem niebezpieczne, jeśli są używane niedbale), ale nawet dynamiczny polimorfizm ma swoją statyczną wersję w C ++: CRTP .

Odkryłem, że większość książek typu „ Efektywny C ++ ” autorstwa Scotta Meyersa lub „ Wyjątkowy C ++” ” autorstwa Herba Suttera jest zarówno łatwa do czytania, jak i skarbnica informacji o znanych i mniej znanych cechach C ++.

Jednym z moich ulubionych jest taki, który powinien sprawić, że włosy każdego programisty Java wyrosną z horroru: w C ++ najbardziej obiektowym sposobem dodania funkcji do obiektu jest użycie funkcji niebędącej składową nieprzyjazną, zamiast funkcja (czyli metoda klasowa), ponieważ:

  • W C ++ interfejs klasy to zarówno jej funkcje składowe, jak i funkcje niebędące składowymi w tej samej przestrzeni nazw

  • nieprzyjazne funkcje niebędące członkami nie mają uprzywilejowanego dostępu do wewnętrznej klasy. W związku z tym użycie funkcji składowej zamiast nieprzyjaznej, niebędącej składnikiem, osłabi hermetyzację klasy.

To zawsze zaskakuje nawet doświadczonych programistów.

(Źródło: między innymi, internetowy Guru tygodnia Herba Suttera nr 84: http://www.gotw.ca/gotw/084.htm )


+1 bardzo dokładna odpowiedź. jest niekompletny z oczywistych powodów (w przeciwnym razie nie byłoby już „ukrytych funkcji”!): p w pierwszym punkcie na końcu odpowiedzi wspomniałeś o członkach interfejsu klasy. czy masz na myśli ".. czy zarówno jego funkcje składowe, jak i przyjacielskie funkcje nie będące członkami"?
wilhelmtell,


o czym wspominasz z 1, musi być wyszukiwaniem koenig, prawda?
Özgür

1
@wilhelmtell: Nie, nie, nie ... :-p ... MAM na myśli "jego funkcje składowe i funkcje niebędące członkami NON-FRIEND" .... Koenig's Lookup zapewni, że te funkcje zostaną rozpatrzone wcześniej niż inne " outside "funkcjonuje w poszukiwaniu symboli
paercebal

7
Świetny post i +1 szczególnie za ostatnią część, z czego zdaje sobie sprawę zdecydowanie zbyt mało osób. Prawdopodobnie dodałbym również bibliotekę Boost jako „ukrytą funkcję”. Uważam, że jest to standardowa biblioteka, którą powinien mieć C ++. ;)
jalf

118

Jedną z funkcji języka, którą uważam za nieco ukrytą, ponieważ nigdy o niej nie słyszałem przez cały czas w szkole, jest alias przestrzeni nazw. Nie zwróciłem na to mojej uwagi, dopóki nie natknąłem się na przykłady w dokumentacji boost. Oczywiście, skoro już o tym wiem, możesz go znaleźć w każdym standardowym dokumencie C ++.

namespace fs = boost::filesystem;

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

1
Myślę, że jest to przydatne, jeśli nie chcesz używać using.
Siqi Lin

4
Jest również przydatny jako sposób na przełączanie się między implementacjami, niezależnie od tego, czy wybierzesz, powiedzmy bezpieczną dla wątków, a nie bezpieczną dla wątków, lub wersję 1 kontra 2.
Tony Delroy,

3
Jest to szczególnie przydatne, jeśli pracujesz nad bardzo dużym projektem z dużymi hierarchiami przestrzeni nazw i nie chcesz, aby nagłówki powodowały zanieczyszczenie przestrzeni nazw (i chcesz, aby deklaracje zmiennych były czytelne dla człowieka).
Brandon Bohrer,

102

W części init forpętli można deklarować nie tylko zmienne , ale także klasy i funkcje.

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

To pozwala na wiele zmiennych różnych typów.


31
Miło wiedzieć, że możesz to zrobić, ale osobiście naprawdę starałbym się unikać czegoś takiego. Przede wszystkim dlatego, że jest trudny do odczytania.
Zoomulator,

2
Właściwie to, co działałoby w tym kontekście, to użycie pary: for (std :: pair <int, float> loop = std :: make_pair (1,2); loop.first> 0; loop.second + = 1)
Valentin Heinitz

2
@Valentin, więc polecam spróbować zgłosić błąd w VS2008 zamiast obniżać głosowanie na ukrytą funkcję. To ewidentnie wina twojego kompilatora.
Johannes Schaub - litb

2
Hmm, to też nie działa w msvc10. Jakie to smutne :(
avakar

2
@avakar faktycznie, gcc wprowadził błąd, który sprawia, że ​​jest odrzucany w wersji 4.6
Johannes Schaub - litb

77

Operator tablicy jest asocjacyjny.

A [8] jest synonimem * (A + 8). Ponieważ dodawanie jest asocjacyjne, można to przepisać na * (8 + A), co jest synonimem ..... 8 [A]

Nie powiedziałeś przydatnych ... :-)


15
Właściwie, używając tej sztuczki, powinieneś naprawdę zwrócić uwagę na typ, którego używasz. A [8] to w rzeczywistości ósme A, podczas gdy 8 [A] to liczba całkowita Ath zaczynająca się od adresu 8. Jeśli A jest bajtem, masz błąd.
Vincent Robert

38
masz na myśli „przemienny”, gdzie mówisz „skojarzony”?
DarenW

28
Vincent, mylisz się. Rodzaj Anie ma żadnego znaczenia. Na przykład, gdyby Abył a char*, kod nadal byłby ważny.
Konrad Rudolph

11
Pamiętaj, że A musi być wskaźnikiem, a nie operatorem przeciążenia klasy [].
David Rodríguez - dribeas

15
Vincent, w tym musi istnieć jeden typ całkowity i jeden typ wskaźnikowy, i ani C, ani C ++ nie dbają o to, który z nich jest pierwszy.
David Thornley,

73

Niewiele wiadomo, że związki mogą być również szablonami:

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

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

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

Mogą też mieć konstruktory i funkcje składowe. Po prostu nie ma nic wspólnego z dziedziczeniem (w tym funkcjami wirtualnymi).


Ciekawy! Czy więc musisz zainicjować wszystkich członków? Czy zachowuje zwykłą kolejność struktur, co oznacza, że ​​ostatni element zostanie zainicjowany „na” wcześniejszych członków?
j_random_hacker

j_random_hacker och, prawda, to nonsens. dobry chwyt. napisałem to tak, jakby to była struktura. czekaj, naprawię to
Johannes Schaub - litb

Czy to nie wywołuje niezdefiniowanego zachowania?
Greg Bacon

7
@gbacon, tak, wywołuje niezdefiniowane zachowanie, jeśli Fromi Tosą odpowiednio ustawione i używane. Taka unia może być jednak używana ze zdefiniowanym zachowaniem ( Tojako tablica bez znaku lub struktura dzieląca sekwencję początkową From). Nawet jeśli używasz go w nieokreślony sposób, może być przydatny do pracy na niskim poziomie. W każdym razie to tylko jeden przykład szablonu unii - mogą istnieć inne zastosowania unii opartej na szablonach.
Johannes Schaub - litb

3
Ostrożnie z konstruktorem. Zauważ, że musisz skonstruować tylko pierwszy element i jest to dozwolone tylko w C ++ 0x. Zgodnie z obecnym standardem musisz trzymać się typów, które są trywialnie konstruowane. I żadnych destruktorów.
Potatoswatter

72

C ++ to standard, nie powinno być żadnych ukrytych funkcji ...

C ++ jest językiem wieloparadygmatycznym, możesz postawić ostatnie pieniądze na ukryte funkcje. Jeden z wielu przykładów: metaprogramowanie szablonów . Nikt w komitecie normalizacyjnym nie chciał, aby język uzupełniający Turinga był wykonywany w czasie kompilacji.


65

Inną ukrytą funkcją, która nie działa w C, jest funkcjonalność jednostki jednoargumentowej + operatora . Możesz go używać do promowania i niszczenia różnych rzeczy

Konwersja wyliczenia na liczbę całkowitą

+AnEnumeratorValue

A wartość modułu wyliczającego, która wcześniej miała swój typ wyliczenia, ma teraz doskonały typ liczby całkowitej, który może pasować do jego wartości. Ręcznie prawie nie znasz tego typu! Jest to potrzebne na przykład wtedy, gdy chcesz zaimplementować przeciążony operator do wyliczenia.

Uzyskaj wartość ze zmiennej

Musisz użyć klasy, która używa statycznego inicjatora w klasie bez definicji poza klasą, ale czasami nie można jej połączyć? Operator może pomóc w utworzeniu tymczasowego bez robienia założeń lub zależności od jego typu

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

Rozpad tablicę na wskaźnik

Czy chcesz przekazać dwa wskaźniki do funkcji, ale to po prostu nie zadziała? Operator może pomóc

// 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

Niewiele osób wie o czasie życia tymczasowych obiektów tymczasowych powiązanych z odniesieniami do stałych. A przynajmniej jest to moja ulubiona część wiedzy C ++, o której większość ludzi nie wie.

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

3
Czy możesz rozwinąć? Jak się tylko drażnisz;)
Joseph Garvin,

8
ScopeGuard ( ddj.com/cpp/184403758 ) to świetny przykład, który wykorzystuje tę funkcję.
MSN

2
Jestem z Josephem Garvinem. Proszę nas oświecić.
Peter Mortensen

Właśnie zrobiłem w komentarzach. Poza tym jest to naturalna konsekwencja użycia parametru odniesienia const.
MSN,


52

Przyjemną funkcją, która nie jest często używana, jest blok try-catch obejmujący całą funkcję:

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

Głównym zastosowaniem byłoby przetłumaczenie wyjątku na inną klasę wyjątków i ponowne zgłoszenie lub tłumaczenie między wyjątkami i obsługą kodu błędu opartego na zwracaniu.


Nie sądzę, że możesz returnz catch bloku Function Try, tylko powtórz.
Constantin

Właśnie spróbowałem skompilować powyższe i nie dało to żadnego ostrzeżenia. Myślę, że powyższy przykład działa.
vividos

7
return jest zabronione tylko dla konstruktorów. Funkcja konstruktora try block wychwytuje błędy inicjalizujące bazę i składowe (jedyny przypadek, w którym funkcja try block robi coś innego niż tylko próba wewnątrz funkcji); brak ponownego rzucenia spowodowałby niekompletny obiekt.
puetzk

Tak. To jest bardzo przydatne. Napisałem makra BEGIN_COM_METHOD i END_COM_METHOD, aby wychwytywać wyjątki i zwracać HRESULTS, aby wyjątki nie wyciekały z klasy implementującej interfejs COM. Działało dobrze.
Scott Langham

3
Jak wskazał @puetzk, jest to jedyny sposób obsługi wyjątków wyrzucanych przez cokolwiek z listy inicjalizatora konstruktora , na przykład konstruktory klas bazowych lub członków danych.
anton.burger

44

Wiele osób zna identity/ idmetafunkcję, ale jest dla niej niezły przypadek użycia dla przypadków innych niż szablony: Łatwość pisania deklaracji:

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

Bardzo pomaga odszyfrować deklaracje C ++!

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

Ciekawe, ale początkowo miałem więcej problemów z odczytaniem niektórych z tych definicji. Innym sposobem, aby rozwiązać problem wewnątrz-out z deklaracjami c ++ jest napisać kilka aliasów typu szablonu: template<typename Ret,typename... Args> using function = Ret (Args...); template<typename T> using pointer = *T;-> pointer<function<void,int>> f(pointer<function<void,void>>);lub pointer<void(int)> f(pointer<void()>);lubfunction<pointer<function<void,int>>,pointer<function<void,void>>> f;
bames53

42

Dość ukrytą funkcją jest to, że możesz definiować zmienne w ramach warunku if, a jego zakres będzie obejmował tylko bloki if i else:

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

Niektóre makra tego używają, na przykład w celu zapewnienia pewnego „zablokowanego” zakresu, na przykład:

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) { /* ... */ }
}

Również BOOST_FOREACH używa go pod maską. Aby to zrealizować, jest to możliwe nie tylko w if, ale także w przełączniku:

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

i za chwilę pętla:

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

(a także w stanie). Ale nie jestem pewien, czy te wszystkie są przydatne :)


Schludny! Nigdy nie wiedziałem, że możesz to zrobić ... zaoszczędzi to (i pozwoli) trochę kłopotów podczas pisania kodu z wartościami zwracanymi przez błąd. Czy jest jakiś sposób, aby nadal mieć warunek zamiast tylko! = 0 w tej formie? if ((int r = func ()) <0) nie wydaje się działać ...
puetzk

puetzk, nie, nie ma. ale cieszę się, że ci się podoba :)
Johannes Schaub - litb

4
@Frerich, nie jest to w ogóle możliwe w kodzie C. Myślę, że myślisz o tym if((a = f()) == b) ..., ale ta odpowiedź w rzeczywistości deklaruje zmienną w warunku.
Johannes Schaub - litb

1
@Angry jest zupełnie inny, ponieważ deklaracja zmiennej jest od razu testowana pod kątem wartości logicznej. Istnieje również mapowanie do pętli for, które wygląda następująco: for(...; int i = foo(); ) ...;To przejdzie przez ciało, o ile ijest prawdziwe, inicjując je za każdym razem. Pętla, którą pokazujesz, po prostu demonstruje deklarację zmiennej, ale nie deklarację zmiennej, która jednocześnie działa jako warunek :)
Johannes Schaub - litb

5
Bardzo dobrze, z wyjątkiem tego, że nie wspomniałeś, że zamierzonym użyciem tej funkcji było, jak sądzę, dynamiczne rzutowanie wskaźnika.
mmocny

29

Zapobieganie operatorowi przecinka wywoływania przeciążeń operatora

Czasami używasz operatora przecinka, ale chcesz się upewnić, że żaden operator przecinka zdefiniowany przez użytkownika nie przeszkadza, ponieważ na przykład polegasz na punktach sekwencji między lewą a prawą stroną lub chcesz się upewnić, że nic nie koliduje z pożądanym akcja. Tutaj void()do gry wkracza:

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

Zignoruj ​​miejsca, które ustawiłem dla warunku i kodu. Ważny jest void(), co zmusza kompilator do używania wbudowanego operatora przecinka. Może to być przydatne także przy implementowaniu klas cech.


Po prostu użyłem tego, aby zakończyć moją przesadną minę ignorującą . :)
GManNickG

28

Inicjalizacja tablicy w konstruktorze. Na przykład w klasie, jeśli mamy tablicę intas:

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

Możemy zainicjować wszystkie elementy tablicy do wartości domyślnych (tutaj wszystkie elementy tablicy na zero) w konstruktorze jako:

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

6
Możesz to zrobić z dowolną tablicą w dowolnym miejscu.
Potatoswatter

@Potatoswatter: trudniejsze, niż się wydaje, ze względu na najbardziej irytującą analizę. Nie
przychodzi mi do głowy

Jeśli typ tablicy jest typem klasowym, to nie jest to potrzebne, prawda?
Thomas Eding

27

Oooh, zamiast tego mogę wymyślić listę nienawiści do zwierząt:

  • Destruktory muszą być wirtualne, jeśli zamierzasz używać ich w sposób polimorficzny
  • Czasami członkowie są inicjowani domyślnie, czasami nie
  • Klasy lokalne nie mogą być używane jako parametry szablonu (czyni je mniej przydatnymi)
  • specyfikatory wyjątków: wyglądają na przydatne, ale nie są
  • przeciążenia funkcji ukrywają funkcje klasy bazowej z różnymi podpisami.
  • brak użytecznej standaryzacji w internacjonalizacji (przenośny standardowy zestaw znaków, ktoś? Będziemy musieli poczekać do C ++ 0x)

Na plus

  • ukryta funkcja: blokowanie funkcji. Niestety nie znalazłem dla niego zastosowania. Tak, wiem, dlaczego go dodali, ale musisz ponownie wrzucić konstruktora, który sprawia, że ​​jest to bezcelowe.
  • Warto uważnie przyjrzeć się gwarancjom STL dotyczącym ważności iteratora po modyfikacji kontenera, co może pozwolić na wykonanie nieco ładniejszych pętli.
  • Boost - to żadna tajemnica, ale warto z niej skorzystać.
  • Optymalizacja wartości zwracanej (nie jest oczywista, ale jest wyraźnie dozwolona przez normę)
  • Funktory, czyli obiekty funkcyjne, czyli operator (). Jest to szeroko stosowane przez STL. nie jest tajemnicą, ale jest fajnym efektem ubocznym przeciążenia operatorów i szablonów.

16
pet hate: brak zdefiniowanego ABI dla aplikacji C ++, w przeciwieństwie do aplikacji C, których wszyscy używają, ponieważ każdy język może zagwarantować wywołanie funkcji C, nikt nie może zrobić tego samego dla C ++.
gbjbaanb

8
Destruktory muszą być wirtualne tylko wtedy, gdy zamierzasz niszczyć polimorficznie, co różni się nieco od pierwszego punktu.
David Rodríguez - dribeas

2
W C ++ 0x typy lokalne mogą być używane jako parametry szablonu.
tstenner

1
W C ++ 0x destruktory będą wirtualne, jeśli obiekt ma jakiekolwiek funkcje wirtualne (np. Vtable).
Macke

nie zapomnij o NRVO, i oczywiście każda optymalizacja jest dozwolona, ​​o ile nie zmienia wyjścia programu
jk.

26

Możesz uzyskać dostęp do chronionych danych i elementów składowych funkcji dowolnej klasy, bez niezdefiniowanego zachowania iz oczekiwaną semantyką. Czytaj dalej, aby dowiedzieć się, jak to zrobić. Przeczytaj również raport wady na ten temat.

Zwykle C ++ zabrania dostępu do niestatycznie chronionych elementów składowych obiektu klasy, nawet jeśli ta klasa jest twoją klasą bazową

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

To zabronione: Ty i kompilator nie wiecie, na co właściwie wskazuje odniesienie. Może to być Cobiekt, w którym to przypadku klasa Bnie ma żadnego interesu i nie ma pojęcia o swoich danych. Taki dostęp jest udzielany tylko wtedy, gdy xjest odwołaniem do klasy pochodnej lub klasy pochodnej. I może pozwolić dowolnemu fragmentowi kodu na odczytanie dowolnego chronionego elementu członkowskiego, po prostu tworząc klasę „do wyrzucenia”, która odczytuje elementy, na przykładstd::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);
}

Z pewnością, jak widzisz, spowodowałoby to zbyt duże szkody. Ale teraz wskaźniki dla członków pozwalają na obejście tej ochrony! Kluczową kwestią jest to, że typ wskaźnika elementu członkowskiego jest powiązany z klasą, która faktycznie zawiera wspomniany element członkowski, a nie z klasą określoną podczas pobierania adresu. To pozwala nam ominąć sprawdzanie

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

I oczywiście działa to również na std::stackprzykładzie.

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

Będzie to jeszcze łatwiejsze dzięki deklaracji using w klasie pochodnej, która sprawia, że ​​nazwa elementu członkowskiego jest publiczna i odwołuje się do elementu członkowskiego klasy bazowej.

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

Inną ukrytą funkcją jest możliwość wywoływania obiektów klas, które można konwertować na wskaźniki funkcji lub odwołania. Rozpoznanie przeciążenia jest wykonywane na ich wyniku, a argumenty są doskonale przekazywane.

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
}

Są to nazywane „zastępcze funkcje wywoławcze”.


1
Kiedy mówisz, że rozwiązanie przeciążenia jest wykonywane na ich wyniku, czy masz na myśli to, że faktycznie konwertuje je na oba Functory, a następnie rozwiązuje przeciążenie? Próbowałem wydrukować coś w operatorze Func1 * () i operatorze Func2 * (), ale wydaje się, że wybiera prawidłowy, kiedy określa, który operator konwersji wywołać.
nawigator

3
@navigator, tak, koncepcyjnie konwertuje do obu, a następnie wybiera najlepsze. Nie musi ich faktycznie nazywać, ponieważ na podstawie typu wyniku wie, co już dadzą. Właściwa rozmowa jest zakończona, gdy okazuje się, że ostatecznie wybrano.
Johannes Schaub - litb

26

Ukryte funkcje:

  1. Czyste funkcje wirtualne mogą mieć implementację. Typowy przykład, czysty wirtualny destruktor.
  2. Jeśli funkcja zgłasza wyjątek niewymieniony w specyfikacjach wyjątków, ale funkcja ma std::bad_exceptionw specyfikacji wyjątku, wyjątek jest konwertowany na std::bad_exceptioni generowany automatycznie. W ten sposób będziesz przynajmniej wiedział, że bad_exceptionzostał rzucony. Przeczytaj więcej tutaj .

  3. funkcja try bloków

  4. Słowo kluczowe template w ujednoznacznianiu typów definicji w szablonie klasy. Jeśli nazwa specjalizacji szablonu członkiem pojawia po ., ->lub ::operatora, a nazwa ta ma wyraźnie wykwalifikowanych parametrów szablonu, poprzedź nazwę użytkownika szablonu z szablonu słów kluczowych. Przeczytaj więcej tutaj .

  5. wartości domyślne parametrów funkcji można zmienić w czasie wykonywania. Przeczytaj więcej tutaj .

  6. A[i] działa tak dobrze, jak i[A]

  7. Tymczasowe instancje klasy można modyfikować! Funkcję składową inną niż stała można wywołać na obiekcie tymczasowym. Na przykład:

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

    Przeczytaj więcej tutaj .

  8. Jeśli dwa różne typy występują przed i po wyrażeniu operatora :ternary ( ?:), to wynikowy typ wyrażenia jest tym, który jest najbardziej ogólnym z nich. Na przykład:

    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 Daddy: A [i] == * (A + i) == * (i + A) == i [A]
abelenky

Otrzymuję komutację, po prostu oznacza to, że [] nie ma własnej wartości semantycznej i jest po prostu odpowiednikiem zamiany w stylu makra, gdzie "x [y]" jest zastępowane przez "(* ((x) + (y ))) ”. Wcale nie tego się spodziewałem. Zastanawiam się, dlaczego tak to zdefiniowano.
P Daddy

Wsteczna kompatybilność z C
jmucchiello

2
Odnośnie pierwszego punktu: Jest jeden szczególny przypadek, w którym trzeba wdrożyć czystą funkcję wirtualną pure wirtualne destruktory.
Frerich Raabe

24

map::operator[]tworzy wpis, jeśli brakuje klucza i zwraca odwołanie do domyślnej wartości wpisu. Możesz więc napisać:

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;

Jestem zdumiony, ilu programistów C ++ o tym nie wie.


11
A po przeciwnej stronie nie możesz użyć operatora [] na mapie const
David Rodríguez - dribeas

2
+1 dla Nicka, ludzie mogą oszaleć, jeśli o tym nie wiedzą .find().
LiraNuna

lub „ const map::operator[]generuje komunikaty o błędach”
po prostu ktoś

2
Nie jest to funkcja języka, jest to funkcja biblioteki szablonów Standard. Jest to również dość oczywiste, ponieważ operator [] zwraca prawidłowe odniesienie.
Ramon Zarazua B.

2
Musiałem używać map w C # przez jakiś czas, gdzie mapy nie zachowują się w ten sposób, aby zdać sobie sprawę, że jest to funkcja. Wydawało mi się, że bardziej mnie to denerwuje niż używałem, ale wygląda na to, że się myliłem. Brakuje mi tego w C #.
sbi

20

Umieszczanie funkcji lub zmiennych w bezimiennej przestrzeni nazw unieważnia użycie w staticcelu ograniczenia ich do zakresu plików.


„deprecates” to mocne określenie…
Potatoswatter

@Potato: Stary komentarz, wiem, ale standard mówi, że użycie statycznego w zakresie przestrzeni nazw jest przestarzałe, z preferencją dla nienazwanych przestrzeni nazw.
GManNickG

@GMan: nie ma sprawy, nie sądzę, żeby tak strony naprawdę „umarły”. Tylko dla obu stron tej historii, staticw zakresie globalnym nie jest w żaden sposób przestarzały. (Dla porównania: C ++ 03 §D.2)
Potatoswatter

Aha, przy bliższym przeczytaniu: „Nazwa zadeklarowana w globalnej przestrzeni nazw ma zasięg globalnej przestrzeni nazw (zwany także zasięgiem globalnym)”. Czy to naprawdę to oznacza?
Potatoswatter

@Potato: Tak. :) staticużycie powinno być używane tylko w ramach typu klasy lub funkcji.
GManNickG,

19

Definiowanie zwykłych funkcji znajomych w szablonach klas wymaga szczególnej uwagi:

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! 

W tym przykładzie dwie różne instancje tworzą dwie identyczne definicje - co jest bezpośrednim naruszeniem ODR

Dlatego musimy upewnić się, że parametry szablonu szablonu klasy pojawiają się w typie dowolnej funkcji zaprzyjaźnionej zdefiniowanej w tym szablonie (chyba że chcemy zapobiec więcej niż jednej instancji szablonu klasy w określonym pliku, ale jest to raczej mało prawdopodobne). Zastosujmy to do odmiany naszego poprzedniego przykładu:

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>*) 

Zastrzeżenie: wkleiłem tę sekcję z C ++ Templates: The Complete Guide / Section 8.4


18

Funkcje void mogą zwracać wartości void

Mało znane, ale poniższy kod jest w porządku

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

Jak również ten dziwnie wyglądający

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

Wiedząc o tym, możesz skorzystać w niektórych obszarach. Jeden przykład: voidfunkcje nie mogą zwracać wartości, ale możesz też nie zwracać niczego, ponieważ mogą być tworzone z wartością non-void. Zamiast przechowywać wartość w zmiennej lokalnej, co spowoduje błąd dla void, po prostu zwróć wartość bezpośrednio

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

Wczytaj plik do wektora ciągów:

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

istream_iterator


8
Lub: wektor <string> V ((istream_iterator <string> (cin)), istream_iterator <string>);
UncleBens

5
masz na myśli vector<string> V((istream_iterator<string>(cin)), istream_iterator<string>());- brak nawiasów po drugim parametrze
knittl

1
To nie jest tak naprawdę ukryta funkcja C ++. Więcej funkcji STL. STL! = Język
Nick Bedford

14

Możesz tworzyć szablony pól bitowych.

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

Nie wymyśliłem jeszcze żadnego celu, ale na pewno mnie to zaskoczyło.


1
Zobacz tutaj, gdzie ja niedawno zasugerował to dla n-bitowej arytmetyki: stackoverflow.com/questions/8309538/...
sehe

14

Jedna z najciekawszych gramatyk ze wszystkich języków programowania.

Trzy z tych rzeczy należą do siebie, a dwie są czymś zupełnie innym ...

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

Wszystkie oprócz trzeciego i piątego definiują SomeTypeobiekt na stosie i inicjalizują go (z uw pierwszych dwóch przypadkach i domyślnym konstruktorem w czwartym. Trzeci to deklaracja funkcji, która nie przyjmuje parametrów i zwraca a SomeType. Piąty jest podobnie deklarowany funkcja, która przyjmuje jeden parametr według wartości typu SomeTypeo nazwie u.


czy jest jakaś różnica między 1 a 2? chociaż wiem, że są to obie inicjalizacje.
Özgür

Comptrol: Nie sądzę. Oba kończą się wywołaniem konstruktora kopiującego, mimo że pierwszy WYGLĄDA jak operator przypisania, tak naprawdę jest konstruktorem kopiującym.
abelenky

1
Jeśli u jest innym typem niż SomeType, to pierwszy z nich wywoła najpierw konstruktor konwersji, a następnie konstruktor kopiujący, podczas gdy drugi wywoła tylko konstruktor konwersji.
Eclipse

3
Pierwsza to niejawne wywołanie konstruktora, druga to jawne wywołanie. Spójrz na poniższy kod, aby zobaczyć różnicę: #include <iostream> class sss {public: explicite sss (int) {std :: cout << "int" << std :: endl; }; sss (double) {std :: cout << "double" << std :: endl; }; }; int main () {sss ddd (7); // wywołuje konstruktor int sss xxx = 7; // wywołuje podwójny konstruktor return 0; }
Kirill V. Lyadvinsky

True - pierwsza linia nie zadziała, jeśli konstruktor zostanie zadeklarowany jawnie.
Eclipse

12

Pozbycie się deklaracji forward:

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

Pisanie instrukcji przełączających z?: Operatorami:

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

Robi wszystko w jednej linii:

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

Zerowanie struktur bez memsetu:

FStruct s = {0};

Normalizowanie / owijanie wartości kąta i czasu:

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

Przypisywanie referencji:

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

2
FStruct s = {};jest jeszcze krótsza.
Constantin

W ostatnim przykładzie byłoby prostsze z: a (); b(); float c = 1.0f;
Zifre

2
Ta składnia "float c = (a (), b (), 1.0f);" jest przydatny do akcentowania operacji przypisania (przypisanie "c"). Operacje przypisania są ważne w programowaniu, ponieważ jest mniej prawdopodobne, że staną się przestarzałymi IMO. Nie wiem dlaczego, może to mieć coś wspólnego z programowaniem funkcjonalnym, w którym stan programu jest ponownie przypisywany do każdej ramki. PS. I nie, „int d = (11,22,1.0f)” będzie równe „1”. Testowane minutę temu z VS2008.
AareP

2
+1 Nie powinieneś dzwonić main ? Sugeruję global().main();i po prostu zapomnieć o Singleton ( można po prostu praca z tymczasowej, która dostaje to żywotność rozszerzony )
sehe

1
Wątpię, aby przypisywanie referencji było przenośne. Uwielbiam jednak możliwość odstąpienia od deklaracji.
Thomas Eding

12

Trójskładnikowy operator warunkowy ?:wymaga , aby jego drugi i trzeci operand miały „zgodne” typy (mówiąc nieformalnie). Ale to wymaganie ma jeden wyjątek (gra słów zamierzona): albo drugi, albo trzeci operand może być wyrażeniem rzucającym (który ma typ void), niezależnie od typu drugiego operandu.

Innymi słowy, za pomocą ?:operatora można napisać następujące, poprawnie poprawne wyrażenia C ++

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

Przy okazji, fakt, że wyrażenie throw jest w rzeczywistości wyrażeniem (typu void), a nie instrukcją, jest kolejną mało znaną cechą języka C ++. Oznacza to między innymi, że poniższy kod jest całkowicie poprawny

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

chociaż nie ma sensu robić tego w ten sposób (może w jakimś ogólnym kodzie szablonu może się to przydać).


Co jest warte, Neil ma pytanie na ten temat: stackoverflow.com/questions/1212978/… , tylko po dodatkowe informacje.
GManNickG

12

Zasada dominacji jest przydatna, ale mało znana. Mówi się, że nawet jeśli w nieunikalnej ścieżce przez kratkę klasy bazowej, wyszukiwanie nazw dla częściowo ukrytego elementu członkowskiego jest unikalne, jeśli element członkowski należy do wirtualnej klasy bazowej:

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

Użyłem tego do zaimplementowania obsługi wyrównania, która automatycznie określa najściślejsze dopasowanie za pomocą reguły dominacji.

Dotyczy to nie tylko funkcji wirtualnych, ale także nazw typedef, elementów statycznych / niewirtualnych i czegokolwiek innego. Widziałem, że jest używany do implementowania nadpisywalnych cech w metaprogramach.


1
Schludny. Jakiś szczególny powód, który podałeś struct Cw swoim przykładzie ...? Twoje zdrowie.
Tony Delroy
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.