1

Nawiązując do niedoskonałości naszych cross-assemblerów proponuję przyjrzeć się, jak sobie radzą poważne komisje standaryzacyjne języków programowania, a nuż czegoś się nauczymy.

laoo/ng napisał/a:

@epi: Proszę bardzo. Nie wiem jak MADS, ale C++ ma z poprzednikiem dokładnie tyle kompatybilności ile trzeba oraz nie zawiera on żadnych nie do końca przemyślanych ficzerów dodanych z powodu braku porozumienia wśród tych, którym na nich zależało, co z kolei implikuje, że nie istnieją żadne rzekome nieoczywiste szczególne przypadki na styku tychże nieistniejących ficzerów. Nie ma również żadnych śladów nieskoordynowanego rozwoju o czym łatwo się przekonać analizując tryb pracy komisji standaryzacyjnej, a mnogość stylów programowania jest pożądaną cechą i oczywistą konsekwencją wieloparadygmatowości języka.
To oczywiście subiektywna opinia i jeżeli jednak uważasz inaczej, to fajnie jakbyś podał jakieś przykłady, do których można byłoby się odnieść, ale to już chyba w jakimś innym wątku, bo głupio tu tak perfidnie offtopikować.

Jestem laikiem w temacie C++, poproszę o odpowiedzi na pytania:

1. Którą implementację byś wybrał i dlaczego:
a. void foo(Bar *bar) { /* kod */ }
b. void foo(Bar &bar) { /* kod */ }

2. Potrzebujesz obsługi błędów w przenośnym kodzie. Użyjesz:
a. wyjątków
b. kodów błędów
c. czegoś innego
Uzasadnij odpowiedź.

3. Operujesz łańcuchami znaków. Użyjesz:
a. std::string
b. const char *
c. LPCTSTR
d. klasy z ulubionego frameworku (jakiego?)
e. własnej implementacji klasy string

4. Operatory logiczne zwracają:
a. bool
b. int

5. Implementujesz przenośną bibliotekę. Użyjesz:
a. C++
b. C

6. Duży projekt w C++ może wymagać obchodzenia (workarounds) błędów kompilatorów:
a. prawda
b. kompletna bzdura

7. Instrukcja C w rodzaju: char *s = malloc(size);
a. jest bardzo często spotykana, więc C++ utrzymało kompatybilność
b. stanowi błąd w C++, bo nie trzeba kompatybilności (dlaczego?). Uzasadnij wyższość składni wymaganej przez C++.

8.
a. #include <iostream.h>
b. #include <iostream>

9. Dziedziczenie wielobazowe:
a. Jest konstrukcją nowoczesnych języków programowania
b. Zostało zaimplementowane dla zgodności z C
c. Jest nazywane "goto programowania obiektowego"

10. this jest:
a. referencją
b. wskaźnikiem
Uzasadnij decyzję projektową.

11. Program

#include <iostream>

using namespace std;

class A
{
public:
    int x;
};

static void printArray(const A *tab, int len)
{
    for (int i = 0; i < len; i++)
        cout << tab[i].x << endl;
}

class B : public A
{
public:
    char *p;
};

int main()
{
    B tab[3];
    tab[0].x = 1;
    tab[1].x = 2;
    tab[2].x = 3;
    printArray(tab, 3);
    return 0;
}

a. wypisze liczby 1,2,3
b. powoduje błąd kompilacji
c. kompiluje się z ostrzeżeniem
d. inna odpowiedź (opis)

12. Visual C++ 2010 przy kompilacji programu:

#include <iostream>

int main()
{
    return 0;
}

z opcją /Wall:
a. nie wyświetli ostrzeżeń podczas kompilacji
b. wyświetli ostrzeżenia (dlaczego?)

https://www.youtube.com/watch?v=jofNR_WkoCE

2

Czemu alternatywna drogą poszło Objective-C ?

3 Ostatnio edytowany przez epi (2012-05-02 10:18:59)

laoo: Widzę, że Fox mnie uprzedził (czego się obawiałem), niemniej nie zamierzam się uchylać od odpowiedzi. :)

Resztki kompatybilności z poprzednikiem

Prawie wszystko, co można napisać w C89 jest poprawnym kodem w C++. Prawie, bo m.in. wiele niejawnych rzutowań wskaźników już się nie skompiluje. To akurat dobrze, ale w takim razie po co na siłę trzymać inne dziwactwa z C, wśród których najbardziej upierdliwym jest powielanie tekstu w nagłówkach?

drobne, niepasujące do siebie i nie do końca przemyślane featury powrzucane naraz z powodu braku porozumienia wśród tych, którym na nich zależało;

Tak w moim odczuciu wyglądają featury wprowadzone jako "lepsze" zastępniki featur z C, jak biblioteczne vector i iostream.
Ten pierwszy nadal wymaga przekazania dwóch obiektów (co do których nikt nie sprawdza, czy mają coś wspólnego ze sobą lub z istniejącym jeszcze kontenerem), gdy chcemy podać do funkcji podzakres.
Ten drugi "świetnie" separuje formatowanie od danych:

std::cout << "mam " << std::hex << n << " lat";
// ups, na dodatek zapomniałem posprzątać!

Mimo nowych lepszych featur, biblioteka C nadal tam jest. Niezastąpiona?

liczne zaskakujące i nieoczywiste szczególne przypadki na styku tychże featur, będące śladem nieskoordynowanego rozwoju,

- Klasyk z lekcji 1:

string s1("q"); // utworzenie zmiennej s1 typu string
string s2(); // deklaracja funkcji s2 zwracającej string

- Wspomniany wyżej iostream zwraca kody błędów, choć mógłby rzucać wyjątkami.
- a && b; oblicza a i jeśli wynik jest != 0, oblicza też b. No, chyba że ktoś zdefiniował operator&&, który pasuje do a i b, wtedy zawsze oblicza a i b.
- Przytoczysz z pamięci reguły wiązania?

oraz milion odległych od siebie stylów pisania.

Daj spokój z hypem o wieloparadygmatowości, są niezałatwione sprawy bliżej ziemi. ;)
- class czy typename?
- using namespace czy pełne ścieżki?
- jeśli zmienna jest out lub in/out, to użyjesz wskaźnika czy referencji?
- ++i czy i++ (gdzie ten drugi można sobie zadeklarować samemu, używając, uwaga: operator++(T&, int nieuzywany);)

Hitler, Stalin, totalniak, SSman, NKWDzista, kaczor dyktator, za długo byłem w ChRL, wypowiadam się afektywnie.

4

BartoszP: objective c powstawalo rownolegle do c++, w czasie gdy ten byl jeszcze w powijakach. procz apple chyba tylko gnustepowcy go uzywaja.

The UNIX Guru`s view of Sex:
unzip; strip; touch; finger; mount; fsck; more; yes; umount; sleep

5 Ostatnio edytowany przez laoo/ng (2012-05-02 14:32:45)

@fox:

  1. Obie wersje kompilują się do tego samego, więc o żadnej optymalizacji nie może być mowy, ale ze względu na gwarancje języka mają odmienną semantykę, przez co są używane w innych sytuacjach. Granica jest subtelna i często płynna i niekiedy świadomie przekraczana, ale pozwala na wyrażenie intencji programisty.
    Wersji a używam w dwóch kontekstach:

    • gdy bar jest dużym obiektem ze sterty oraz gdy foo potrzebuje go użyć wołając metod nie-stałych (wybieram wersję void foo(Bar const* bar) gdy chcę ograniczyć wołalność do stałych metod). W grę wchodzi tylko wołanie metod (względnie dobieranie się do pól, jeśli to struktura). Niedopuszczalne jest nadpisanie ( *bar = bang ) oraz zapamiętanie wskaźnika na później, gdyż dostając goły wskaźnik nie mamy gwarancji jego żywotności – goły wskaźnik wpadający do funkcji każe domniemać, że to obiekt tymczasowy do natychmiastowego użytku. Nawiasem mówiąc goły wskaźnik nigdy nie powinien pojawić się poza argumentem funkcji, gdyż nie ma on semantyki własności (na co C++owcy są bardzo uczuleni), stąd przykazanie użyj – nie zapamiętuj. Jego użycie jest uzasadnione tylko byciem wspólnym mianownikiem dla standardowych wskaźników unique_ptr (o unikalnej właśności) oraz shared_ptr (o dzielonej właśności – jest to wskaźnik z licznikiem odwołań),

    • gdy bar ma służyć jako parametr opcjonalny i jego pustość niesie dodatkową informację, którą możemy wykorzystać przy projektowaniu algorytmu, a więc tak jak w C.

    Wybieram b, gdy intencją jest modyfikacja bar, który najczęściej jest obiektem automatycznym. W szczególności bar może być dużą strukturą, którą potrzebujemy wypełnić w funkcji foo. Skoro rolą foo jest wypełnienie bar, to nie ma ona sensownego zachowania, gdyby bar był pustym wskaźnikiem i tu przydaje się gwarancja istnienia bar.
    Nie wiem jak bardzo świadome było pytanie, ale tak samo jak void foo(Bar * bar) ma brata foo(Bar const* bar), to void foo(Bar & bar) ma o wiele popularniejszego brata void foo(Bar const& bar), który zabrania modyfikacji bar. Jest on używany w kontekstach korzystania z bar bez jego modyfikowania gdy operujemy na wartości. Jest to zatem klasyczne wykorzystanie referencji jako aliasa na wartość, co jest niezbędne przy dociążaniu operatorów. Celowo napisałem wartość, gdyż do takiego foo możemy podać literał, czy wyrażenie obliczające się do Bar, a więc niekoniecznie musi być to konkretna zmienna o istniejącym adresie, tak jak przy wskaźnikach. Stałych referencji używamy przy definiowaniu operatorów, konstruktorów kopiujących, klauzul łapania wyjątków itd.
    Nie mogę jeszcze nie wspomnieć o kuzynie: r-value referencji, przez co programista C++ widzi jeszcze wersję c:
    c. void foo(Bar && bar) { /* kod */ }
    Jest to bardzo silny mechanizm, gdyż ta funkcja dowiąże się tylko to wartości, która nie ma nazwy, a więc wartości tymczasowej, która zginie zaraz po zakończeniu działania funkcji foo, dzięki czemu możemy ukraść jej zasoby, bo i tak nikt tego nie zauważy. Na bazie r-value referencji budowane są w C++ konstruktory przenoszące, czyli takie, które tworzą nowy obiekt na bazie starego obiektu tego samego typu, który zaraz zostanie zniszczony – stąd możemy bezpiecznie ukraść mu pamięć, jeśli jest to np. jakiś kontener.
    Zagadnienie jak widać jest bardzo szerokie, ale wspomnę, że dzięki konstruktorom przenoszącym wcale nie jest głupia konstrukcja czwarta:
    d. void foo(Bar bar) { /* kod */ }
    Jest to wzorzec dla settera, czyli metody, która przejmuje bar na własność. foo mogło być wywołane z wartością tymczasową (więc bar „przeniósł” ją do siebie) lub z wartością nazwaną (wówczas wykonała się kopia). Ciało foo w takiej okoliczności powinno przenieść zawartość bar do pola klasy, której jest metodą. Dzięki temu mamy jedną metodę i żadnego kopiowania, jeśli nie jest ono konieczne.

  2. Należy odróżnić błędy od sytuacji wyjątkowych. Gdy parsuję tekst napisany przez użytkownika, błędy w takim tekście nie są niczym wyjątkowym, lecz czymś spodziewanym i aktywnie testowanym. Wyjątki są tu nie na miejscu. Gdy jednak zabraknie mi pamięci, albo nie znajdę karty graficznej, na której ma działać moja gra, to już jest sytuacja wyjątkowa, o którą nie powinienem się martwić w normalnym toku działania programu i tylko skrajni pesymiści przy każdej alokacji itd. sprawdzają czy się udała. C++ jest językiem dla optymistów, których interesuje pozytywna ścieżka wykonania programu (tzw. ścieżka sukcesu) i nieoczekiwane niepowodzenia traktują jako sytuacje wyjątkowe, dla których piszą obsługę w odpowiednich miejscach programu. Wyjątki są mechanizmem na tyle przenośnym na ile wspiera je kompilator i system operacyjny (a poza jakimiś dzikimi embedami są wpierane wszędzie) i powinny być używane wszędzie z wyjątkiem systemów czasu rzeczywistego, czy fragmentów wrażliwych na czas wykonania, gdyż nie istnieje deterministyczna metoda stwierdzenia maksymalnego czasu obsługi wyjątku. Gdy żaden wyjątek nie zostanie rzucony, we współczesnych implementacjach nie ma żadnego narzutu na czas wykonania programu. Dodatkowy czas obsługi możemy spokojnie zaniedbać, gdyż tyczy on się sytuacji… wyjątkowych. Gdy ktoś uważa inaczej, to znaczy że sytuacja, o której myśli, wcale nie jest wyjątkowa i powinien przeprojektować algorytm.
    W standardowym C++ nie można ignorować istnienia wyjątków, gdyż standardowy operator new rzuca wyjątek w przypadku braku pamięci, co jest kluczowe w implementacji standardowych kontenerów, które zakładają, że instrukcja zaraz po alokacji, ma zaalokowaną pamięć. Wzorem biblioteki standardowej zakładając, że wszystko się udaje, upraszczamy dramatycznie kod, gdyż nie trzeba sprawdzać, czy udało się to, czy udało się tamto… po prostu piszemy kod, tak jakby się udało, a sytuację, gdy się nie uda, obsługujemy jako sytuację wyjątkową.

  3. Do 90% operacji na łańcuchach znaków użyję std::string. Jest to klasa z semantyką własności względem pamięci zajmowanej przez napis i z podstawowymi operacjami edycyjnymi. Const char * to tylko wskaźnik, więc nie jest de facto łańcuchem znaków, a LPCTSTR ma różne znaczenie w zależności od unicode’owości (i nie jest to typ standardowy), więc trudno je porównywań. Frameworków nie używam, ale zdarza mi się użyć kilku narzędzi napisowych z boosta. Klasy string sam nie implementuję, bo już jest. Co najwyżej mogę zaimplementować coś, co bardziej nadaje się do konkretnego zastosowania i co załatwia kilka procent z pozostałych 10%, gdyż std::string nie jest 100% wpasowana w STL, gdyż nie jest klasycznym kontenerem danych. Jest napisem.

  4. Operatory logiczne i relacyjne zwracają wartość typu bool, a w praktyce wystarczy coś konwertowalnego do bool, gdyż operacjom selekcji wystarczy wyrażenie, które da się do boola skonwertować. Tak działa kompatybilność z C, że taki „if” nie bierze inta równego albo różnego od zera, tylko boola, a int potrafi się do niego skonwertować (oczywiście statycznie, w run time wszystko jest po staremu, C++ tylko lepiej dba o typy wyrażeń).

  5. Użyję C++. Nie znam architektury, którą warto się zainteresować, która nie wspierałaby C++.

  6. Prawda. Wszystkie kompilatory mają błędy. Sam kilka znalazłem i zgłosiłem Microsoftowi. Wskaż mi duży projekt w C, który nie obchodzi błędów kompilatora, szczególnie, że C jako język super przenośny powinien obsługiwać X platform i Y kompilatorów, z których każdy ma swoje kruczki. Prawdę mówiąc nawet zdarzają się błędy języka. Wystarczy zerknąć na issue list komitetu standaryzacyjnego.

  7. Ta składnia stanowi w C++ błąd. W C można było po cichu zrzutować void* na cokolwiek miało się tylko ochotę, dzięki czemu wskaźnik na cokolwiek zwracany przez malloc mógł być wprost zinterpretowany jako wskaźnik na to co chcemy. C++ na to nie pozwala, gdyż w C++ typ jest graczem pierwszoplanowym i bezpieczeństwo typów jest jego kluczową własnością. Dlaczego tak jest lepiej? W C mogłeś np. napisać int *i = malloc(1); co dla początkującego programisty, który nie czyta dokumentacji, wygląda całkiem sensownie, a niesie ze sobą brzemienne skutki. C++ zniechęca do takiej konstrukcji oferując natywny odpowiednik w postaci int * i = new int[1]. Nie jest to najszczęśliwszy zapis, ale jest to prosty odpowiednik, który dba o typ wyrażenia. Operator new jest karmiony typem jaki chcemy zaalokować oraz opcjonalnie liczbą elementów (a nie liczbą bajtów) i zwróci wskaźnik o typie int*, który z ochotą zostanie przypisany do zmiennej i. Nie potrzeba tu żadnego rzutowania, a mamy bezpieczeństwo typów. Dodatkowo new woła konstruktor tworzonego obiektu, co nam załatwia wstępną inicjalizacje pamięci, czego w C nie mieliśmy, a destruktor wołany po delete (bo nota bene zapomniałeś napisać w swoim przykładzie free i doznałeś wycieku ;p) woła destruktor, który sprząta po obiekcie (szczególnie takim nietrywialnym).
    A jak już jesteśmy przy alokacji zasobów, żaden (przyzwoity) programista C++ nie trzyma pamięci jako gołe wskaźniki, bo wie, że może zapomnieć o delete (albo nawet jak nie zapomni, to może mu w tym przeszkodzić wyjątek) i skrupulatnie dba o własność zasobu wg metodologii RAII i raczej napisałby std::unique_ptr<int> i( new int[1] ); przez co miałby pewność, że zasób zostanie automatycznie zwolniony (poprzez zawołanie destruktora obiektu std::unique_ptr<int>) w momencie zamknięcia bloku zawierającego i. Programiści C++ są leniwi i nie dbają o te wszystkie detale, z którymi walczą programiści w C. Oni zwalają je na kompilator.

  8. Oczywiście #include <iostream>. iostream nie jest nagłówkiem C, stąd brak rozszerzenia. W momencie, gdy zastanawiano się jakie rozszerzenie dać nagłówkom C++ (hxx? hpp?), ktoś wpadł na pomysł, że nie trzeba dawać żadnego :)

  9. Pytanie jest tendencyjne ;) Wszystkie tak zwane „nowoczesne języki programowania” mają dziedziczenie wielobazowe. Z tą różnica, że np. Java czy C# pozwala na dziedziczenie tylko interfejsów, co nazywa ich implementowaniem. W C++ interfejsy są pod postacią klas czysto wirtualnych, więc de facto można definiować sobie interfejsy i je implementować. C++ pozwala na więcej. Pozwala, aby klasy bazowe miały implementacje metod, a nawet miały pola. Dzięki czemu obiekt pochodny może posiadać wiele podobiektów bazowych. Można nawet zadeklarować niektóre z podobiektów jako wirtualne, przez co będą one występowały tylko raz. Jak nie czujesz się na siłach, aby korzystać z tych mechanizmów – nie rób tego i używaj tylko klas czysto wirtualnych (tzw. interfejsów), ale jeśli wiesz co robisz, to można za pomocą tego systemu zaimplementować wiele ciekawych funkcjonalności, które jednak mogą wykraczać poza „kurs podstawowy”. C++ daje narzędzia i od umiejętności programisty zależy jak ich użyje (łącznie ze zrobieniem sobie krzywdy).

  10. this jest wskaźnikiem i jest wskaźnikiem, bo tak. ;)

  11. Odpowiedź to d. Jest to program, w którym programista C dowodzi, że nie rozumie interakcji dziedziczenia z tablicami w stylu C i pokazuje, że C++ pozwala mu na zrobienie sobie krzywdy. Nieźle mnie to zaskoczyło, bo sam bym nie wpadł na to, żeby napisać coś takiego. Klasa A zawiera jedno pole int. Klasa B zawiera podobiekt A z jednym polem int, oraz pole char*. Deklarując tablicę obiektów typu B przydzielasz każdemu pamięci na dwa pola (int i char*). Tu popełniasz pierwszy błąd. Na razie niewielki. Deklarujesz tablicę jak w starym dobrym C. Drugi błąd (ten już brzemienny) wiąże się z potraktowaniem tablicy jako wskaźnika na (jeden!) obiekt pochodny, który bardzo ładnie i grzecznie potrafi zrzutować się na wskaźnik na (jeden!) obiekt bazowy. Takie rzutowanie jest fajne, bo obiekt pochodny potrafi wszystko to, co bazowy, a nawet więcej, więc możemy traktować jakby był obiektem bazowym. Ale problem jest taki, że przekazałeś ten wskaźnik do funkcji, która oczekuje tablicy (!) obiektów bazowych. Są to dwa różne obiekty, a oszukałeś system typów raz uważając to za wskaźnik, a raz za tablicę. Z pierwszym elementem idzie pięknie, bo ta operacja jest zdefiniowana, ale pakujesz się w problemy wykonując operacje na wskaźnikach do obiektu pochodnego myśląc, że to obiekt bazowy, dzięki czemu pudłujesz w drugi element. Nie jest to poprawny program w C++ i jego wynik jest niezdefiniowany (łącznie z możliwością wytworzenia czarnej dziury pochłaniającej wszechświat). Nadużyłeś dobroduszności C++ w sposób nie do wykrycia przez kompilator i zrobiłeś siebie krzywdę pokroju int * i = malloc( 5 ). Jak poprawnie robi się takie rzeczy? Otóż w C++ nie używamy gołych tablic C w ogólności, a szczególnie jeśli operujemy na obiektach, które nie są obiektami C (a więc obiektach z dziedziczeniem, Ty popełniłeś ten błąd). W C++ można w tym miejscy użyć statycznej tablicy std::array<B,3>, albo kontenera std::vector<B>. Obu obiektów nie da się zrzutować na wskaźnik i nie udałoby Ci się napisać takiego programu, który wygląda sensownie, a takim nie jest. Stroustrup też ma coś do powiedzenia na ten temat.

  12. /Wall w kompilatorze Microsoftu jest bardzo pedantyczne i często wyświetla uwagi, które nie raportują zagrożenia, ale mogą dać wskazówkę programiście o zachowaniu, którego mógł nie być świadom, stąd zaincludowanie iostream wygeneruje stado komunikatów w stylu, że w jakiejś strukturze na końcu dodano trzy bajty paddingu, albo kompilator zadecydował, że nie będzie inlinował jakiejś tam funkcji zadeklarowanej jako inline i tym podobne błahostki. Odpowiedź zatem to b, gdyż w iostream kompilator dodał kilka paddingów i odpuścił sobie kilku inline’owań. Dobrym poziomem warningów jest /W3, ale też nie jest idealnie, bo wśród ostrzeżeń czwartego poziomu jest kilka moim zdaniem przydatnych i te włączam sobie manualnie (wymuszając ich poziom na trzeci).

EDIT: w punkcie 3. 95% + 10% != 100%. Poprawiłem :)

6

@epi:
Kompatybilność z C jest oczywista – aby korzystać z bazy bibliotek napisanych w C, które kompilują się po kosmetycznych poprawkach / uściśleniach. Mógłbyś podać jednak kilka konkretów tych dziwactw? Nie twierdze, że C ich nie miał, ale np. nie rozumiem uwagi o powielaniu tekstu w nagłówkach? Chciałbyś żeby C++ porozumiewał się pomiędzy modułami jak C# - bez includów? To chyba inna para kaloszy nie możliwa do sensownego zaimplementowania.
Nie za bardzo czuję obiekcje to std::vector. Rozumiem, że boisz się pomieszania iteratorów, ale ja tu nie widzę żadnego problemu. Jak piszesz w C to też nie masz pewności, że dwa wskaźniki wskazują na tę samą tablicę, ale w przypadku vectora tak źle nie jest. Np. w microsoftowej implementacji STLa można włączyć feature „checked iterators”, dzięki któremu operacje na iteratorach dokonują dodatkowych sprawdzeń czy dotyczą tego samego kontenera itd. Dzięki niej spokojnie wykryjesz takie błędy, a najlepsze jest to, że to jest opcja, którą w każdej chwili możesz wyłączyć i cieszyć się pełną wydajnością równoważną operacjom na wskaźnikach bez żadnego ukrytego sprawdzania zakresów jak w C# czy w Javie
Uwagi do iostream niestety nie rozumiem. Wyjaśnij proszę.
Biblioteka C jest dla kompatybilności, jakbyś chciał skompilować kod w C. Nikt nie każe Ci jej używać, bo powszechnie wiadomo, że biblioteka C++ jest lepsza ;)
iostream i wyjątki: nie zgodzę się. Operacje na strumieniach są naturalnie narażone na niepowodzenia (użytkownik podał literkę, a chciałeś cyfre, plik się skończył itd.). Wyjątki w C++ są od sytuacji wyjątkowych, które chciałbyś obsługiwać w innym miejscu, bo nie chcesz dbać o nie w miejscu użycia. W przypadku strumieni ich użycie jest takie lokalne, że gdzie pisałbyś tę obsługę? Pewnie tuż pod użyciem. I czy to ma sens?  Lepiej sprawdzić status jeśli Cię to interesuje. Dodatkowo pachnie to już sterowaniem przez wyjątki. Zupełnie jak ten idiotyzm w .NET, w którym metoda int.Parse rzuca wyjątek jak się nie uda, i musieli ratować się dodając metodę TryParse.
Co do „deklaracji funkcji s2 zwracającej string” to masz rację. Jest to pewna niezręczność… chociaż przepraszam. To była niezręczność, gdyż C++11 do inicjalizacji wprowadził notację klamrową, a więc teraz piszemy string s1{"q"}  i string s2{}. A ten błąd jest i tak tylko powierzchowny, bo wychodzi przy pierwszej próbie kompilacji użycia s2.
a && b – winny. Nie możesz zdefiniować leniwego && dla swojego typu. Tu jest faktycznie pewne zamieszanie i trzeba uważać. Dociążania operatorów nie powinno uczyć studentów zbyt wcześnie, bo to jednak zaawansowany feature i raczej przeznaczony dla twórców bibliotek.
Czy przytoczę z pamięci reguły wiązania? Nie. Ale nawet programista C powinien wiedzieć, że tam gdzie ma wątpliwości, to czytelnik kodu też pewnie będzie je miał i trzeba dodać nawiasy. Czy to wada C++?
class czy typename? To jest wg ciebie jakiś palący problem? Ja w deklaracjach szablonów typowych raczej piszę typename, ale to jest sprawa indywidualna i też zależna od kontekstu, a nawet można potraktować to jako komentarz wyrażający intencje programisty.
using namespace? Tu też jest sprawa indywidualna, ale w moim środowisku using jest traktowane jak element złego stylu programowania. W każdym bądź ma swoich miłośników i jakieś tam zastosowania. Nie widzę w tym nic złego.
Zmienna In/out? Odpowiedziałem w pierwszym punkcie Foxowi.
Domyślnie oczywiście ++i, a i++ tylko tam, gdzie to potrzebne. A operator++(T&, int nieuzywany); to rzeczywiście hack :)

Ogólnie bardzo niezbornie wyraziłeś te swoje uwagi i średnio przekonująco (z wyjątkiem przypadkowej deklaracji funkcji czy prawie leniwych operatorów). Ogólnie nie widzę, dlaczego wg Ciebie powinno to dyskredytować w jakiś sposób C++.

7

Dziękuję za wyczerpujące odpowiedzi i jednocześnie chylę czoło.

laoo/ng napisał/a:

mnogość stylów programowania jest pożądaną cechą i oczywistą konsekwencją wieloparadygmatowości języka

Zupełnie jak w Perlu, popieram. :)

https://www.youtube.com/watch?v=jofNR_WkoCE

8 Ostatnio edytowany przez IRCer (2012-05-02 15:14:29)

Fox napisał/a:

Dziękuję za wyczerpujące odpowiedzi i jednocześnie chylę czoło.

laoo/ng napisał/a:

mnogość stylów programowania jest pożądaną cechą i oczywistą konsekwencją wieloparadygmatowości języka

Zupełnie jak w Perlu, popieram. :)

Czesciowo tak, ale takze jak w Pythonie:
http://c2.com/cgi/wiki?OoppExploringThe … adigmShift
http://sequoia.cs.byu.edu/lab/files/pub … n2004a.pdf
OO++ to logiczny krok naprzod w duchu idei "right screwdriver for the right screw", dogmatyczne 1990s OOP w stylu Javy wydaje sie byc dzisiaj cokolwiek przestarzale i niepotrzebnie ograniczajace (powinienem moze powiedziec 1970s, ale Smalltalka pewnie i tak juz malo kto dzisiaj pamieta ;]).

laoo/ng napisał/a:

@epi:
class czy typename? To jest wg ciebie jakiś palący problem? Ja w deklaracjach szablonów typowych raczej piszę typename, ale to jest sprawa indywidualna i też zależna od kontekstu, a nawet można potraktować to jako komentarz wyrażający intencje programisty.

Laoo, ladne odpowiedzi! :-)

Maly dodatek, w przypadku template template parameters /* http://www.informit.com/articles/article.aspx?p=376878 */ jest roznica pomiedzy "typename" a "class":

template <typename T, template <typename> class Cont = Deque> // legalne
template <typename T, template <typename> typename Cont = Deque> // nielegalne

Co zreszta pasuje do wyrazania-intencji-programisty (szczegolnie jesli w pozostalych 99.99% przypadkach zawsze piszemy "typename") i ma intuicyjny sens (w koncu jako TTP faktycznie oczekujemy class template).

9

Laoo, serdeczne dzięki, że chciało Ci się poświęcić tyle czasu i zużyć klawiaturę na tak szczegółowe odpowiedzi. Ja z C++ kontakt miałem, ale jestem dętka w rozumieniu tych wszystkich kruczków, które tu wyjaśniłeś (myślę w C :) ) i wiele mi to wyjaśnia / pomaga. Znakomity tutorial.


laoo/ng napisał/a:

[*]Użyję C++. Nie znam architektury, którą warto się zainteresować, która nie wspierałaby C++.[/*]

Ekhm, a małe Atari? :)

The problem is not the problem; the problem is your attitude about the problem

10

Dziękuję za uznanie włożonego wysiłku, bo 1/3 dnia to pisałem. Polecam się też na przyszłość. Jakby kogoś paliły jakieś pytania, to chętnie odpowiem :)
Dla tych, co chcą posłuchać jaki fajny jest C++11 to polecam ten wykład autora. Stroustrup był nawet z nim kilka tygodni temu na Uniwerku Wrocławskim, ale niestety moją wersję "Języka C++" ukradli mi na studiach i nie dostałem autografu ;)

@wieczor: No to mnie przyszpiliłeś z tym małym atari. Nie mam teraz innego wyjścia, jak napisać na niego kompilator C++ ;)

11

haha, najlepiej natywny ;)

The UNIX Guru`s view of Sex:
unzip; strip; touch; finger; mount; fsck; more; yes; umount; sleep

12

W sumie... był kiedyś taki kompilator Watcom. Tzn był komercyjny - teraz jest Open Source. Ma on produkcje kodu na różne platformy, można by dopisać do niego target na 6502/Atari - nie wiem jak to jest tam fizycznie zorganizowane, może na zasadzie modułów/pluginów? Sam parser wówczas już jest z głowy. Kod jaki to produkowało na MS-DOS był tak optymalny że opad szczęki - co fajne to zamiast wypluwania binarki można skompilować do źródła dla makroassemblera jakby ktoś chciał jeszcze usprawniać.

The problem is not the problem; the problem is your attitude about the problem

13

ten kompilator nadal jest, jak sam wspomniales jako opensrors, tj. open watcom.
fame swoja zawdziecza temu ze kiedys byl najoptymalniejszym (ale to bylo daaawno temu, gcc czy nawet borland c jeszcze przed 2000 byly od niego szybsze) i mial najbardziej kitowy opis bledow (tj. dostawales kod bledu, ktorego opis musiales odszukac w papierowej dokumentacji, takiej bez hyperlinkow ;) ).

zdecydowanie prosciej bylo by dopisac ta obsluge do gcc czy llvm tylko komu by sie chcialo i po co? przeciez laoo tylko zartowal ;)

The UNIX Guru`s view of Sex:
unzip; strip; touch; finger; mount; fsck; more; yes; umount; sleep

14

laoo/ng napisał/a:

Kompatybilność z C jest oczywista – aby korzystać z bazy bibliotek napisanych w C, które kompilują się po kosmetycznych poprawkach / uściśleniach. Mógłbyś podać jednak kilka konkretów tych dziwactw? Nie twierdze, że C ich nie miał, ale np.

Uściślenia w bibliotekach napisanych w C na pewno im nie zaszkodziły, natomiast z argumentem odnośnie korzystania z bibliotek napisanych w C nie mogę się zgodzić. Inne języki nie mają z tym problemu, mimo że składniowo od C są bardziej odległe. Jak to możliwe? Łatwiej chyba jest skompilować kod w C kompilatorem C i przekazać zadanie połączenia linkerowi.

Wśród innych dziwactw wymieniłbym m.in. daleko posuniętą wymienność pojęć tablicy i wskaźnika, ułatwiającą generowanie niewykrywalnych błędów, jak ten zaproponowany przez Foxa. Wymieniłbym również składnię deklaracji, której niezgodności z intuicją dowiedli już jej autorzy, pisząc translator na język angielski. :)

laoo/ng napisał/a:

nie rozumiem uwagi o powielaniu tekstu w nagłówkach? Chciałbyś żeby C++ porozumiewał się pomiędzy modułami jak C# - bez includów? To chyba inna para kaloszy nie możliwa do sensownego zaimplementowania.

Tak, chciałbym i mam to w D, dzięki czemu nie muszę uwzględniać prywatnych składowych w publicznym interfejsie ani chować ich za pImpl, jak również nie muszę się martwić, czy któryś z dołączanych nagłówków jest wrażliwy na to, co było wcześniej w dołączającym kodzie. No i nade wszystko nie muszę się pisać i utrzymywać powielonego tekstu.

laoo/ng napisał/a:

Nie za bardzo czuję obiekcje to std::vector. Rozumiem, że boisz się pomieszania iteratorów, ale ja tu nie widzę żadnego problemu. Jak piszesz w C to też nie masz pewności, że dwa wskaźniki wskazują na tę samą tablicę, ale w przypadku vectora tak źle nie jest.

Jak piszę w C to nie mam tej pewności, ale C++ jest ponoć lepszy. :)

laoo/ng napisał/a:

Np. w microsoftowej implementacji STLa można włączyć feature „checked iterators”, dzięki któremu operacje na iteratorach dokonują dodatkowych sprawdzeń czy dotyczą tego samego kontenera itd. Dzięki niej spokojnie wykryjesz takie błędy, a najlepsze jest to, że to jest opcja, którą w każdej chwili możesz wyłączyć i cieszyć się pełną wydajnością równoważną operacjom na wskaźnikach bez żadnego ukrytego sprawdzania zakresów jak w C# czy w Javie

Implementacja microsoftowa jest, jak rozumiem, poza standardem, ale to już coś. Przyjrzę się, jak to wygląda w implementacjach STLa, których mam szansę użyć prędzej niż tej z MS.

laoo/ng napisał/a:

Uwagi do iostream niestety nie rozumiem. Wyjaśnij proszę.

Nie podoba mi się, że formatowane wyjście przeplata formatowanie z danymi do wyświetlenia oraz składnia formatowania jest rozwlekła. Nie podoba mi się również, że obiekt reprezentujący standardowe wyjście przechowuje stan definiujący formatowanie i nie zawsze mam kontrolę nad tym, w jakim stanie zostawi go kod, który wywołuję.
W nowszych rozwiązaniach to podejście nie wygląda na zbyt chętnie powielane, co moim zdaniem jest argumentem za tym, że nie było ono dobrze przemyślane.

laoo/ng napisał/a:

Biblioteka C jest dla kompatybilności, jakbyś chciał skompilować kod w C. Nikt nie każe Ci jej używać, bo powszechnie wiadomo, że biblioteka C++ jest lepsza ;)

No właśnie kiedy chciałbym jej użyć, ponieważ w niektórych sytuacjach znajduję jej rozwiązania wygodniejszymi, to cały czas czuję nad sobą bat "biblioteka C to zło". ;)
Natomiast chcąc skompilować kod w C, użyję kompilatora C.

laoo/ng napisał/a:

iostream i wyjątki: nie zgodzę się. Operacje na strumieniach są naturalnie narażone na niepowodzenia (użytkownik podał literkę, a chciałeś cyfre, plik się skończył itd.).

Miałem na myśli nieco tylko poważniejsze problemy, jak urwany kabel czy zniknięcie karty pamięci, na które normalnie nie jesteś przygotowany i zwykle rozwiązanie w postaci zwinięcia stosu byłoby najwygodniejsze. Na szczęście jednak byłem niedouczony – doczytałem sobie, że mogę już wybrać (co się chwali), czy chcę być obrzucony wyjątkiem. Niestety nieopatrznie upaprałem sobie spodnie grząskim gruntem. ;)

W pozostałych sytuacjach, o których mówisz, jak parsowanie wejścia od usera – pełna zgoda, wyjątki tylko utrudniają.

laoo/ng napisał/a:

Czy przytoczę z pamięci reguły wiązania? Nie. Ale nawet programista C powinien wiedzieć, że tam gdzie ma wątpliwości, to czytelnik kodu też pewnie będzie je miał i trzeba dodać nawiasy. Czy to wada C++?

Chyba mówimy o czymś innym. Mam na myśli wiązanie identyfikatora z funkcją, która się za nim kryje, co (przynajmniej dla mnie, być może standardem są hardkorowcy, którzy to pojmują) nie jest łatwe ze względu na szablony i przeciążenia. Czy to wada? Jeśli za jedno z kryteriów w ocenie jakości narzędzia przyjmiemy łatwość jego użycia, to skomplikowanie jest wadą.

Co do ostatniej części (x czy y), to masz rację, czepiam się, natomiast znajomość konwencji, a zwłaszcza jej umotywowania, wymaga sporo nauki, a pogodzenie się z nią bywa ciężkie, jeśli nie pasuje do przyzwyczajenia lub trudno ją powiązać z intuicją.

laoo/ng napisał/a:

Ogólnie bardzo niezbornie wyraziłeś te swoje uwagi i średnio przekonująco (z wyjątkiem przypadkowej deklaracji funkcji czy prawie leniwych operatorów). Ogólnie nie widzę, dlaczego wg Ciebie powinno to dyskredytować w jakiś sposób C++.

Dziękuję za wyczerpującą odpowiedź, mam nadzieję, że udało mi się ją wykorzystać do uściślenia uwag niezbornych i nieprzekonujących.
Nie zamierzałem dyskredytować C++, ponieważ trochę go (masochistycznie ;)) lubię i nadal używam, choć już nie tak intensywnie jak, powiedzmy, 2 lata temu i już swoich umiejętności w nim nie rozwijam.
Nie pod tym kątem więc były moje uwagi. Chciałem jedynie wykazać, że podobnie jak MADS, C++ został zaprojektowany z uwzględnieniem kompatybilności z poprzednikiem na poziomie kodu źródłowego, a w toku jego rozwoju dodawano do niego często nowe featury (znów to brzydkie słowo), które nie zawsze dobrze pasowały do już istniejących, a rzadko decydowano się na potencjalne zerwanie zgodności z istniejącym kodem.
Skutkiem tego w obu istnieje bagaż, którego zrozumienie wymaga znajomości ich ewolucji. Domagam się zatem równego traktowania błędogennych cech MADSa i C++. :)

Hitler, Stalin, totalniak, SSman, NKWDzista, kaczor dyktator, za długo byłem w ChRL, wypowiadam się afektywnie.

15

Przespałem się z tym i wydaje mi się, że większość Twoich obiekcji wiąże się z kompatybilnością z C i to nie chodzi Ci o stopień kompatybilności, ale o sam jej fakt. Jeśli tak postawimy sprawę, to z tego co pamiętam sam Stroustrup nie ukrywa, że jest z tym bardzo dużo problemów i jak wykazałeś, nie trzeba ich długo szukać. Inaczej jednak być nie mogło i nie będzie. Pierwszym projektem Stroustrupa był „C with classes”, z którego ewoluował C++, więc ścisłe powiązanie z C jest faktem, a stopień kompatybilności nie jest przypadkowy – jest najlepszy jaki udało się osiągnąć na zasadach kompromisu. Z tego co widzę, to potrzebujesz po prostu języka D i wytykasz w C++ te fragmenty, w których przeziera w nim C, a które zostały zrealizowane inaczej w D, porzucając tę kompatybilność. Niestety pod tym względem nie wiele można podyskutować, bo C++ już taki jest, inny nie będzie, a dyskusja przerodziłaby się w debatę teologiczną :)
Nie za bardzo mogę jednak się zgodzić, że winę za to ponosi komitet standaryzacyjny. Zapewniam Cię, że do C++ nie dostaje się nic nieprzemyślanego. Czytając ich listę mailingową można się przekonać ile rozpatrują propozycji zmian w C++, jak długo szlifowane są obiecujące zmiany i ile z nich naprawdę przechodzi. Każda zmiana wiąże się z ryzykiem złamania istniejącego kodu i decyzje są podejmowane bardzo ostrożnie. A najwięcej problemów jest nie z tym, co zostało dodane do C++, a z tym co jest dziedziczone z C, więc teoria o braku porozumienia pomiędzy autorami ficzerów jest bardzo naciągana. Fory mieli ludzie od D, bo napisać język od nowa wzorując się na C++, C# itp. już jest łatwe ;)
W kwestii konkretów nie podoba Ci się formatowanie wyjścia. Mi też się nie podoba, ale trzeba pamiętać, że jest to niskopoziomowy i bardzo ogólny mechanizm strumieni, które można konfigurować na takie przetwarzanie elementów, jakie potrzebujemy. Strumień ze swej natury nie jest niestety poręczny w przypadku formatowania innego niż domyślne i najważniejszy problem jaki tu widzę, to że nie istnieją standardowe wrappery ułatwiające nietrywialne formatowanie. Nie wiem, czy to wina nieprzemyślenia architektury, ale najwidoczniej uznano, że bardziej wyuzdane formatery byłyby bardziej narażone na krytykę ze względu na mnogość preferencji użytkowników w tym temacie i zdecydowano się na standaryzowanie tylko najprostszego modelu, pozostawiając rozwój konkretnym użytkownikom wedle ich potrzeb. Ze swojej strony mogę polecić boost::format.
Z regułami dowiązywania rzeczywiście nie zrozumiałem pytania. To o co pytasz, to bardzo specjalistyczna działka i moje stanowisko jest takie, że dopóki nie jesteś twórcą biblioteki, to nie powinieneś się o to martwić. Reguły są ustalone tak, aby wszystko działało i nawet cieszę się, że nie muszę się o to martwić i poza brzegowymi przypadkami, w których na własne życzenie pakuję się w kłopoty, rzeczywiście się nie martwię. Cały temat jest zresztą jeszcze trudniejszy odkąd w C++11 są r-value referencje i jak ostatnio się tym zajmowałem, to musiałem przestać w obawie przed eksplozją głowy. Ciekawi mnie, czy w D te reguły są prostsze ;)
Co do postawionej tezy, to zgadzam się, że C++ cierpi na kompatybilność z C bardzo podobnie, jak MADS cierpi na kompatybilność z QA, ale wina leży tylko w pierwotnym założeniu kompatybilności, które w szerszej perspektywie zdaje się jednak mieć więcej korzyści niż wad – możliwe, że MADS nie stałby się de facto standardem, jeśli nie dałoby się skompilować w nim źródeł z x-asma a pośrednio i z QA. Podobnie, jakby C++ nie potrafił kompilować kodów w C, możliwe, że nie odniósłby takiego sukcesu. Względny sukces języka D (ale tylko względny, bo trudno uznać go jeszcze za język „produkcyjny”) opiera się na wcześniejszym sukcesie C++, C# i Javy.

16

Wygląda na to, że "sprawcą" tych r-value jest w 1/3 Polak:
http://www.artima.com/cppsource/rvalue.html
http://www.open-std.org/jtc1/sc22/wg21/ … n2027.html

17

laoo/ng napisał/a:

Ze swojej strony mogę polecić boost::format.

Boost.Format jest dosc wolny, niestety :-/
Mi sie podoba FastFormat -- szybki (spokojnie konkurujacy ze strumieniami C, snprintf) i porzadnie zaprojektowany (polecam serie artykulow w Overload Journal, ciekawa nawet jesli szczegoly formatowania nas nie interesuja, a samo projektowanie bibliotek owszem).
Niestety, jest wada: ostatni update w 2010 :-(
http://www.fastformat.org/
http://blog.fastformat.org/ // wyzej-wspomniane artykuly dostepne pod naglowkiem "Articles" po prawej

IMHO sila C++ jest wlasnie mozliwosc tworzenia takich bibliotek, zwiazana z wysokim stopien modyfikalnosci samego jezyka (templates, overloading, etc. -- jedynie makra w LISP-ie chyba moga sie tutaj rownac). Nie wydaje mi sie, aby takie biblioteki jak Boost.MPL, Boost.Proto czy Boost.Phoenix byly nawet mozliwe do stworzenia w innych "mainstreamowych" jezykach. Takze takie z elegancka skladnia (i zarazem wysoka wydajnoscia) jak Eigen, http://eigen.tuxfamily.org/dox-devel/, wydaja sie raczej nie-trywialne do osiagniecia -- na wierzchu zapis algebry liniowej w rodzaju A = B + C (dla macierzy i wektorow) a pod spodem intrinsiki SIMD pasujace pod dany CPU (a "posrodku" brak zbednych kopii).

18

Przeczuwałem, że boost::format nie jest demonem prędkości, ale nigdy tego nie zbadałem, bo nie używałem go w miejscach krytycznych czasowo, a jest całkiem funkcjonalny. Fajnie, że są szybkie alternatywy, ale w moim przypadku w firmie staramy się nie zwiększać zależności od zewnętrznych biblioteki i rzadko wychodzimy poza boosta.
Eigen jest przykładem bardzo specjalistycznej biblioteki, w której bardzo dobrze znajdują zastosowanie dociążenia operatorów, bo właśnie do takich rzeczy zostały one pomyślane - do zastosowań, w których użycie operatora jest intuicyjne. Biblioteka byłaby mniej poręczna w użyciu, gdyby musiała działać na funkcjach (tak jak w javie) i dla takich rzeczy, dobrze, że dociążanie operatorów jest możliwe, nawet kosztem pewnych zawirowań, jak z operatorem &&.

19 Ostatnio edytowany przez Fox (2019-02-27 15:17:33)

Polecam obejrzeć:

The next big Thing - Andrei Alexandrescu - Meeting C++ 2018 Opening Keynote

https://www.youtube.com/watch?v=tcyb1lpEHm0

50 shades of C++ - Nicolai Josuttis - Meeting C++ 2018 Closing Keynote

https://www.youtube.com/watch?v=9-_TLTdLGtc

https://www.youtube.com/watch?v=jofNR_WkoCE

20

Dzięki za linki.
Bardzo ciekawe.

ATARI 65XE + SIO2BT
http://atari.pl/hsc/ad.php?i=22.3