Dlaczego użycie krotek w C ++ nie jest bardziej powszechne?


124

Dlaczego nikt nie używa krotek w C ++, ani w Boost Tuple Library, ani w bibliotece standardowej dla TR1? Przeczytałem dużo kodu C ++ i bardzo rzadko widzę użycie krotek, ale często widzę wiele miejsc, w których krotki rozwiązałyby wiele problemów (zwykle zwracając wiele wartości z funkcji).

Krotki pozwalają robić różne fajne rzeczy, takie jak ta:

tie(a,b) = make_tuple(b,a); //swap a and b

To z pewnością lepsze niż to:

temp=a;
a=b;
b=temp;

Oczywiście zawsze możesz to zrobić:

swap(a,b);

Ale co, jeśli chcesz obrócić trzy wartości? Możesz to zrobić za pomocą krotek:

tie(a,b,c) = make_tuple(b,c,a);

Krotki znacznie ułatwiają również zwracanie wielu zmiennych z funkcji, co jest prawdopodobnie znacznie częstszym przypadkiem niż zamiana wartości. Używanie odwołań do zwracania wartości z pewnością nie jest zbyt eleganckie.

Czy są jakieś duże wady krotki, o których nie myślę? Jeśli nie, dlaczego są rzadko używane? Czy są wolniejsze? A może po prostu ludzie nie są do nich przyzwyczajeni? Czy warto używać krotek?


17
+1 za sprytną sztuczkę z zamianą krotek :)
kizzx2

10
a = a ^ b; b = a ^ b; a = a ^ b;
Gerardo Marset

3
Krotki IMO są wygodne w słabych językach pisania lub językach, w których są strukturami natywnymi. Na przykład w Pythonie czy PHP po prostu ułatwiają życie, podczas gdy w C ++ jest za dużo pisania (aby zbudować go z szablonu) i za mało korzyści.
doc

4
Komentarz do PO: Myślę, że obecnie przyjęta odpowiedź jest już przestarzała do tego stopnia, że ​​jest myląca. Możesz ponownie rozważyć wybór zaakceptowanej odpowiedzi.
ulidtko

8
@GerardoMarset Poważnie?
święta

Odpowiedzi:


43

Ponieważ nie jest jeszcze standardem. Wszystko, co niestandardowe, wiąże się z dużo większą przeszkodą. Kawałki Boost stały się popularne, ponieważ programiści ich domagali się. (na myśl przychodzi hash_map). Ale chociaż krotka jest przydatna, nie jest to tak przytłaczająca i wyraźna wygrana, że ​​ludzie się nią przejmują.


1
Wydaje się, że ludzie używają innych części Boost jak szaleni. Chociaż z pewnością mapy skrótów są znacznie bardziej przydatne niż krotki.
Zifre

Nie znam szczegółów tego, co widzisz, ale zgaduję, że części, których ludzie używają jak szalone, są funkcjami, których naprawdę chcieli. W ten sposób (znowu zgadując) popularność mapy skrótów, liczony wskaźnik i tym podobne. Krotka jest przydatna, ale nie jest czymś, co wyskakuje, aby wypełnić dziurę. Wypłata nie jest oczywista. Jak często musisz obracać dokładnie N obiektów? (W przeciwieństwie do konieczności obracania dowolnie długich wektorów). A ludzie są przyzwyczajeni do przekazywania wartości zwracanych przez odwołanie lub zwracania małych klas lub struktur.
Alan De Smet

20
Obecnie jest częścią standardu C ++ 11: en.cppreference.com/w/cpp/utility/tuple
Roman Susi

124

Cyniczną odpowiedzią jest to, że wiele osób programuje w C ++, ale nie rozumie i / lub nie korzysta z funkcjonalności wyższego poziomu. Czasami dzieje się tak dlatego, że im nie wolno, ale wielu po prostu nie próbuje (a nawet nie rozumie).

Jako przykład bez wspomagania: ilu ludzi korzysta z funkcji znajdujących się w <algorithm>?

Innymi słowy, wielu programistów C ++ jest po prostu programistami C używającymi kompilatorów C ++ i być może std::vectori std::list. Jest to jeden z powodów, dla których stosowanie boost::tuplenie jest bardziej powszechne.


18
-1 ode mnie, ponieważ programiści C ++ nie są tak głupi, jak ta odpowiedź sprawia, że ​​brzmią.
user541686

5
@Mehrdad Po przejrzeniu dużej ilości kodu C ++, zarówno komercyjnego, jak i niekomercyjnego, czytając mnóstwo materiałów w C ++, myślę, że całkiem bezpiecznie jest powiedzieć, że bardzo duża część programistów „C ++” to po prostu programiści C, którzy nie mogą Kompilator C. Na przykład szablonów prawie całkowicie brakuje w większości materiałów (coś, co bardzo pokochałem). Dziwne makra są powszechne, a przestrzeń nazw jest poważnie niedostatecznie wykorzystywana.
Wyraźniej

5
Nonsensowna odpowiedź. Jeśli ktoś ich potrzebuje, dogoni ich. Nie są potrzebne; więc nie są używane. Mówienie, że nie są używane, ponieważ nie są łatwe do zrozumienia, jest złe.
Michael Chourdakis

9
@Michael Nonsense comment. Nic nie jest potrzebne w programowaniu, gdy masz kompletny podzbiór języka Turinga. Brak użycia z pewnością nie oznacza, że ​​wszyscy rozumieją konstrukcje C ++ wyższego poziomu i decydują się ich nie używać.
Trey Jackson,

4
Tbh NIGDY nie potrzebowałem std :: tuple poza metaprogramowaniem szablonów wariadycznych. Nie było sensu w życiu, gdybym siadał ze smutną twarzą i myślał: „Gdybym tylko miał te krotki”. W rzeczywistości, kiedy patrzę na krotki, myślę: „Do czego, do diabła, ktoś przy zdrowych zmysłach potrzebowałby ich (nie uważałbym się za normalnego)”. Ludzie spoza metaprogramowania wydają się używać ich jako „struktury anonimowej”, co jest tak brzydkie i wskazuje na silny brak jakości kodu i łatwości utrzymania w ich głowach.
święta

23

Składnia krotki w C ++ może być nieco bardziej szczegółowa, niż by tego chciała większość ludzi.

Rozważać:

typedef boost::tuple<MyClass1,MyClass2,MyClass3> MyTuple;

Więc jeśli chcesz szeroko korzystać z krotek, albo wszędzie dostaniesz kroje czcionek, albo wszędzie otrzymujesz irytująco długie nazwy typów. Lubię krotki. Używam ich w razie potrzeby. Ale zwykle ogranicza się to do kilku sytuacji, takich jak indeks N-elementowy lub użycie multimap do powiązania par iteratorów zakresu. Zwykle ma to bardzo ograniczony zakres.

W porównaniu z czymś takim jak Haskell czy Python wygląda to bardzo brzydko i hackowo. Kiedy pojawi się C ++ 0x i otrzymamy słowo kluczowe „auto”, krotki słów kluczowych zaczną wyglądać o wiele bardziej atrakcyjnie.

Użyteczność krotek jest odwrotnie proporcjonalna do liczby naciśnięć klawiszy wymaganych do ich zadeklarowania, spakowania i rozpakowania.


Większość ludzi zrobi „używając wzmocnienia przestrzeni nazw”; i nie trzeba wpisywać boost ::. Nie sądzę, żeby pisanie krotki było aż tak dużym problemem. To powiedziawszy, myślę, że masz rację. auto może sprawić, że znacznie więcej osób zacznie używać krotek.
Zifre

2
@Zifre: problem polega na tym, że nie powinieneś używać „przestrzeni nazw X” w pliku nagłówkowym, ponieważ wymusza to zanieczyszczenie przestrzeni nazw i podważa przestrzenie nazw.
Pan Fooz

1
Ach tak, zapomniałem o nagłówkach. Ale wewnątrz kodu programu nie musisz się tym martwić. A kiedy już mamy C ++ 0x, możemy użyć auto, co powinno wyeliminować wiele pisania.
Zifre

19
Czy to tylko ja? Nie sądzę, aby zapisywanie 7 znaków „boost ::” było tym, o czym miał na myśli, ale raczej pozostałymi 33 znakami . To cholernie dużo wpisywania nazw klas, zwłaszcza jeśli one również mają zasięg przestrzeni nazw. Weź boost :: tuple <std :: string, std :: set <std :: string>, std :: vector <My :: Scoped :: LongishTypeName>> jako śmieszny przykład.
Ogre Psalm 33

10

Dla mnie to nawyk, bez dwóch zdań: krotki nie rozwiązują dla mnie żadnych nowych problemów, tylko kilka, które już dobrze sobie radzę. Zamiana wartości nadal wydaje się łatwiejsza w staromodny sposób - i, co ważniejsze, tak naprawdę nie myślę o tym, jak zamienić „lepiej”. Jest wystarczająco dobry, jak jest.

Osobiście nie uważam, że krotki są świetnym rozwiązaniem zwracania wielu wartości - brzmi to jak praca dla structs.


4
„Naprawdę nie myślę o tym, jak zamienić„ lepiej ”.” - Kiedy piszę kod, piszę błędy. Zmniejszenie złożoności kodu zmniejsza liczbę błędów, które piszę. Nienawidzę tworzyć ciągle tych samych błędów. Tak, myślę o tym, jak lepiej <strike> zamienić </> kod . Mniej ruchomych części (LOC, zmienne tymczasowe, identyfikatory do błędnego wpisania), bardziej czytelny kod; Dobry kod.
sehe

Zgodzić się. Klasa zawinięta w automatyczny wskaźnik lub inteligentny wskaźnik jest zapisywana. Kiedyś użyłem krotek, ale potem przepisałem kod za pomocą klas. retValue.state jest bardziej przejrzysty niż retValue.get <0> ().
Valentin Heinitz

1
@sehe: Moim celem jest również pisanie lepszego, bardziej czytelnego kodu. Dodawanie większej liczby typów składni wiąże się z kosztami i nie uważam, że „lepsza zamiana” usprawiedliwia myślenie o jeszcze większej liczbie typów składni dla każdego czytanego wiersza kodu.
ojrac

8

Ale co, jeśli chcesz obrócić trzy wartości?

swap(a,b);
swap(b,c);  // I knew those permutation theory lectures would come in handy.

OK, więc przy 4 wartościach etc, ostatecznie n-tuple staje się mniejszym kodem niż n-1 swapów. Z domyślną zamianą powoduje to przypisanie 6 zamiast 4, które miałbyś, gdybyś sam zaimplementował szablon trzystopniowy, chociaż mam nadzieję, że kompilator rozwiąże to dla prostych typów.

Możesz wymyślić scenariusze, w których zamiany są nieporęczne lub nieodpowiednie, na przykład:

tie(a,b,c) = make_tuple(b*c,a*c,a*b);

rozpakowanie jest trochę niewygodne.

Chodzi jednak o to, że istnieją znane sposoby radzenia sobie z najczęstszymi sytuacjami, w których krotki są dobre, a zatem nie ma pilnej potrzeby zajmowania się krotkami. Jeśli nic więcej, nie jestem pewien, że:

tie(a,b,c) = make_tuple(b,c,a);

nie robi 6 kopii, co czyni go całkowicie nieodpowiednim dla niektórych typów (kolekcje są najbardziej oczywiste). Możesz mnie przekonać, że krotki są dobrym pomysłem dla „dużych” typów, mówiąc, że tak nie jest :-)

W przypadku zwracania wielu wartości krotki są idealne, jeśli wartości są niezgodnych typów, ale niektórzy ludzie ich nie lubią, jeśli wywołujący może uzyskać je w złej kolejności. Niektórzy ludzie w ogóle nie lubią wielu zwracanych wartości i nie chcą zachęcać do ich używania, ułatwiając je. Niektórzy ludzie po prostu wolą nazwane struktury dla parametrów wejścia i wyjścia i prawdopodobnie nie można ich przekonać kijem baseballowym do używania krotek. Bez względu na smak.


1
Na pewno nie chciałbyś zamienić wektorów na krotki. Myślę, że zamiana trzech elementów jest z pewnością bardziej przejrzysta w przypadku krotek niż w przypadku dwóch zamiany. Jeśli chodzi o wiele zwracanych wartości, parametry out są złe, struktury wymagają dodatkowego wpisywania i na pewno są przypadki, w których potrzeba wielu zwracanych wartości.
Zifre

wiem, dlaczego pan użyć krotki (a wiem, dlaczego ja ich używać, jeśli przy okazji powstał, chociaż nie sądzę, że kiedykolwiek ma). Zgaduję, dlaczego inni ludzie ich nie używają, nawet jeśli są ich świadomi. np. ponieważ nie zgadzają się z „out params are evil” ...
Steve Jessop

Czy wiesz, czy możemy zastąpić "tie (a, b, c) = make_tuple (b, c, a);" przez "remis (a, b, c) = remis (b, c, a);" ?
Rexxar

2
Remis (no cóż, warstwa technicznie) jest krotką utworzoną z referencjami innymi niż stałe. Nie mogę znaleźć dokumentacji boost, która mówi, co gwarantuje operator = i konstruktor kopiujący dla tie / tuple, gdy niektóre z odnośników mają to samo odwołanie. Ale to właśnie musisz wiedzieć. Naiwna implementacja operatora = najwyraźniej może pójść bardzo źle ...
Steve Jessop

1
@Steve: A ponieważ krawaty mają na celu zapobieganie kopiowaniu (powinny działać dla typów nie do kopiowania; zauważ, że LHS może być czymś zupełnie innym), to wszystko powinno pójść bardzo źle (pomyśl o obiektach innych niż klasa POD). Wyobraź sobie, jak możesz napisać tę samą logikę bez użycia tymczasowych.
sehe

7

Jak wiele osób zauważyło, krotki nie są po prostu tak przydatne, jak inne funkcje.

  1. Zamienianie i obracanie sztuczek to tylko sztuczki. Są całkowicie mylące dla tych, którzy wcześniej ich nie widzieli, a ponieważ to prawie wszyscy, te sztuczki są po prostu kiepską praktyką inżynierii oprogramowania.

  2. Zwracanie wielu wartości przy użyciu krotek jest znacznie mniej dokumentujące niż alternatywy - zwracanie nazwanych typów lub używanie nazwanych odwołań. Bez tego samodokumentowania łatwo jest pomylić kolejność zwracanych wartości, jeśli są one wzajemnie wymienialne i nie być mądrzejszym.


6

Nie każdy może korzystać z boostu, a TR1 nie jest jeszcze powszechnie dostępny.


3
Wiele osób używa Boost. Ci ludzie również mogą używać krotek.
Zifre

3
Zapytałeś, dlaczego ludzie ich nie używają, a ja odpowiedziałem.
Brian Neal

2
Do słabszego wyborcy: tak się składa, że ​​pracuję w miejscu, w którym użycie boostu jest politycznie niemożliwe, a nawet w tej chwili łańcuch narzędzi kompilatora, którego używamy (dla systemu wbudowanego) nie obsługuje TR1 / C ++ 11.
Brian Neal

5

W przypadku używania C ++ w systemach wbudowanych pobieranie bibliotek Boost staje się skomplikowane. Łączą się ze sobą, więc rozmiar biblioteki rośnie. Zwracasz struktury danych lub używasz przekazywania parametrów zamiast krotek. Podczas zwracania krotek w Pythonie struktura danych jest w kolejności, a typ zwracanych wartości po prostu nie jest jawny.


5

Rzadko je widzisz, ponieważ dobrze zaprojektowany kod zwykle ich nie potrzebuje - nie ma zbyt wielu przypadków na wolności, w których użycie struktury anonimowej jest lepsze niż użycie nazwanej. Ponieważ cała krotka naprawdę reprezentuje jest strukturą anonimową, większość programistów w większości sytuacji po prostu pasuje do prawdziwej rzeczy.

Powiedzmy, że mamy funkcję „f”, w której zwrot krotki może mieć sens. Z reguły takie funkcje są zwykle na tyle skomplikowane, że mogą zawieść.

Jeśli "f" CAN zawiedzie, potrzebujesz zwrotu statusu - w końcu nie chcesz, aby wywołujący musieli sprawdzać każdy parametr, aby wykryć awarię. „f” prawdopodobnie pasuje do wzorca:

struct ReturnInts ( int y,z; }
bool f(int x, ReturnInts& vals);

int x = 0;
ReturnInts vals;
if(!f(x, vals)) {
    ..report error..
    ..error handling/return...
}

To nie jest ładne, ale spójrz, jak brzydka jest alternatywa. Zauważ, że nadal potrzebuję wartości statusu, ale kod nie jest bardziej czytelny i nie jest krótszy. Prawdopodobnie jest też wolniejszy, ponieważ ponoszę koszt 1 kopii z krotką.

std::tuple<int, int, bool> f(int x);
int x = 0;
std::tuple<int, int, bool> result = f(x); // or "auto result = f(x)"
if(!result.get<2>()) {
    ... report error, error handling ...
}

Inny, znaczący minus jest tutaj ukryty - dzięki „ReturnInts” mogę dodać zmianę zwrotu „f”, modyfikując „ReturnInts” BEZ ZMIANY INTERFEJSU „f”. Rozwiązanie krotki nie oferuje tej krytycznej funkcji, co sprawia, że ​​jest gorszą odpowiedzią dla dowolnego kodu biblioteki.


1
Wyjątki sprawiają, że ten interfejs jest znacznie bardziej przejrzysty.
David Stone,

Aby być uczciwym (i bardzo późno na imprezę), możesz ułatwić czytelność, ustawiając using std::tuple;i po prostu używając tuplew kodzie.
Alrekr

2
Użycie tuplesprawia, że ​​kod jest mniej czytelny, a nie bardziej. Większość kodów w dzisiejszych czasach zawiera bardzo dużą liczbę symboli - zobaczenie std::tuplejasno pokazuje, co to jest.
Tom Swirly,

3

Z pewnością krotki mogą być przydatne, ale jak wspomniano, jest trochę nad głową i jedna lub dwie przeszkody, przez które musisz przeskoczyć, zanim będziesz mógł ich naprawdę użyć.

Jeśli Twój program konsekwentnie znajduje miejsca, w których musisz zwrócić wiele wartości lub zamienić kilka wartości, warto skorzystać z trasy krotek, ale czasami po prostu łatwiej jest robić rzeczy w klasyczny sposób.

Ogólnie rzecz biorąc, nie każdy ma już zainstalowany Boost i na pewno nie przechodziłbym przez kłopoty z jego pobieraniem i konfigurowaniem moich katalogów dołączania do pracy z nim tylko dla jego funkcji krotek. Myślę, że przekonasz się, że osoby, które już używają Boost, są bardziej skłonne do znajdowania zastosowań krotek w swoich programach niż użytkownicy, którzy nie korzystają z Boost, a migranci z innych języków (przychodzi na myśl Python) są bardziej skłonni do po prostu zmartwienia z powodu braku krotek w C ++ niż zbadanie metod dodawania obsługi krotek.


1

Ponieważ magazyn danych std::tuplema najgorsze cechy zarówno a, jak structi tablicy; cały dostęp jest oparty na n-tej pozycji, ale nie można iterować przez pętlę tupleusing for.

Jeśli więc elementy w tuplesą koncepcyjnie tablicą, użyję tablicy, a jeśli koncepcyjnie elementy nie są tablicą, struktura (która ma nazwane elementy) jest łatwiejsza w utrzymaniu. ( a.lastnamejest bardziej wyjaśniające niż std::get<1>(a)).

To pozostawia transformację wspomnianą przez OP jako jedyny realny przypadek użycia krotek.


0

Mam wrażenie, że wielu używa Boost.Any i Boost.Variant (z pewną inżynierią) zamiast Boost.Tuple.


Dlaczego miałbyś zamienić wydajne statyczne typowanie na coś takiego?
Zifre

Boost.Variant jest całkowicie bezpieczny dla typów.
user21714

1
Ups, tak, jest bezpieczny dla typów, ale umożliwia pisanie w czasie wykonywania.
Zifre

5
Nie wiem, jak można zastąpić krotkę Dowolny / Wariant. Nie robią tego samego.
Mankarse

1
@Zifre Nie mogę mówić w imieniu autora, ale myślę, że implikacją jest tutaj używanie ich razem z innymi typami kontenerów.
Tim Seguine,
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.