Praca w C++? czemu programiści nie lubią C++?

0

Wielu twierdzi, że C++ jest trudne do ogarnięcia, ale moim zdaniem C++ jest bardzo podobne do Java i C#. Chyba jedyną trudnością tego języka jest nauczenie się wskaźników i zarządzania pamięcią... więc dlaczego programiści żywią niechęć wobec tego języka?

IDE do C++ są już coraz lepsze.

praca w C++, a praca w webie? ktoś ma doświadczenie na obu polach i podzieli się swoimi doświadczeniami? jakie są specyficzne różnice?

6

Chyba jedyną trudnością tego języka jest nauczenie się wskaźników i zarządzania pamięcią

Nauczenie się wskaźników to względnie prosta sprawa, ale bolesne jest kopanie się z losowymi segfaultami i dziwacznym zachowaniem programu, gdy pomylisz się w obsłudze wskaźników. Po co tracić życie na coś takiego zamiast implementować jakiś fajny ficzer? Nie każdy odczuwa przyjemność z obcowania ze wskaźnikami.

0

C++ nie jest trudne do ogarnięcia, ale po prostu tracisz dużo więcej czasu na rzeczy, które w innych językach zrobisz szybciej i prawdopodobnie lepiej, bo ktoś bardziej doświadczony dał Ci "podstawę".

7

Chyba jedyną trudnością tego języka jest nauczenie się wskaźników i zarządzania pamięcią...

Mało jeszcze wiesz... :P

1
Złoty Pomidor napisał(a):

Wielu twierdzi, że C++ jest trudne do ogarnięcia, ale moim zdaniem C++ jest bardzo podobne do Java i C#. Chyba jedyną trudnością tego języka jest nauczenie się wskaźników i zarządzania pamięcią... więc dlaczego programiści żywią niechęć wobec tego języka?

Po 8 latach w cpp przesiadek się na c#
Już nie muszę martwić się reference collapising
Kiedy jest Universal ref a kiedy r value

Itp itd

Żeby zarabiać w cpp tyle co w c#, js, java trzeba parę razy więcej wiedzieć

A jak się nie musi to po jakiego grzyba się męczyć

0

Bo większość ludzi zna starego C++ z musem pisania wskaźników, new i innych dziwactw. Kontenery, smart pointery, auto, czy inicjalizacja klamrowa wiele ułatwiają ;)

0

Bo większość ludzi zna starego C++ z musem pisania wskaźników, new i innych dziwactw. Kontenery, smart pointery, auto, czy inicjalizacja klamrowa wiele ułatwiają ;)

Tyle, że jak nawstawiasz wszelkie bajery (sprytne wskaźniki, metody wirtualne, sprawdzanie indeksów tablic, sprawdzanie nullptr przy każdym korzystaniu z wskaźników itp itd) zbliżające C++ np do Javy to zostaniesz z czymś wolniejszym od Javy, więc jaki w tym sens? Sprytne wskaźniki są natomiast ułomne (i to każdy rodzaj sprytnych wskaźników) z bardzo prostego powodu - nie radzą sobie z cyklicznymi referencjami i nie nadają się do współdzielenia stanu (obojętne czy mutowalnego czy nie) między wątkami (bo albo są oparte o kosztowne operacje atomowe albo psują się przy korzystaniu z wielu wątków jednocześnie). Odśmiecanie pamięci (w sensie tracing GC) jest znacznie wygodniejsze i kompletne.

0

A to w języku trzeba pisać tylko dlatego, że jest szybszy niż inny? Gdyby tak było nie wychodziłbym poza C. :)

Ponadto, prosiłbym o jakieś dane potwierdzające tezę o tym, że smart pointery czy .at() przy std::array spowalnia program, bo mam inne informacje. 

1

Z Javy korzysta się zamiast z C w aplikacjach biznesowych bo łatwo ją przenieść (JVM ) i kod jest czytelniejszy.C++ z typi pierdołami typu smart pointery itd. jest mniej czytelny od Javy i wolniejszy od niej (wedlug tego co napisał @Wibowit). Ma więc wady C i wady Javy, zalet nie widze

3

Ponadto, prosiłbym o jakieś dane potwierdzające tezę o tym, że smart pointery czy .at() przy std::array spowalnia program, bo mam inne informacje.

W prostych benchmarkach różnic pewnie dużych nie będzie, bo proste przypadki się prosto optymalizuje. Trzeba by było wziąć jakiś większy algorytm i dorobić wszędzie te rzeczy o których pisałem (sprytne wskaźniki, sprawdzanie nullptr przy każdym wykorzystaniu wskaźnika, sprawdzanie indeksów tablic, itp itd). Zrobiłem jednak prosty benchmark symulujący pointer chasing:

#include <array>
#include <vector>
#include <cstdlib>
#include <chrono>  // for high_resolution_clock
#include <inttypes.h>

int main(int argc, char** argv) {
    std::array<int32_t, 100> tablica;
    for (int32_t i = 0; i < 100; i++) {
        tablica[i] = (i * 34373 + 43) % 100;
    }
    int64_t iterations = 1000000000;

    int64_t op_sum = 0;
    int64_t op_index = 0;
    auto op_start = std::chrono::high_resolution_clock::now();
    for (int64_t i = 0; i < iterations; i++) {
        op_sum += tablica[tablica[tablica[tablica[op_index]]]];
        op_index = i % 100;
    }
    auto op_finish = std::chrono::high_resolution_clock::now();
    std::chrono::duration<double> op_elapsed = op_finish - op_start;

    int64_t at_sum = 0;
    int64_t at_index = 0;
    auto at_start = std::chrono::high_resolution_clock::now();
    for (int64_t i = 0; i < iterations; i++) {
        at_sum += tablica.at(tablica.at(tablica.at(tablica.at(at_index))));
        at_index = i % 100;
    }
    auto at_finish = std::chrono::high_resolution_clock::now();
    std::chrono::duration<double> at_elapsed = at_finish - at_start;

    printf("%" PRId64 ", %lf\n", op_sum, op_elapsed.count());
    printf("%" PRId64 ", %lf\n", at_sum, at_elapsed.count());
    return EXIT_SUCCESS;
}

Wyniki:

49500000041, 1.517305
49500000041, 1.774159

To jest nadal bardzo prosty kod i niespecjalnie wykorzystuje możliwości procesora w zakresie (spekulatywnego) wykonywania kodu poza kolejnością. Na jakimś słabym procku pewnie różnice byłyby znacznie większe.

Sprawdzanie indeksów tablic czy nullptrów można zoptymalizować zarówno podczas kompilacji AOT jak i JIT. Zarówno kompilatory C++ jak i JVM to robią i dla prostych benchmarków nie ma dużej różnicy wydajnościowej między C++, a Javą. Gdy optymalizatory wysiadają zwiększa się spadek wydajności spowodowany tymi bajerami typu sprawdzanie indeksów tablic czy nullptrów.

Poza tym, gdyby sprawdzanie indeksów tablic i nullptrów było całkowicie darmowe w C++ to wtedy zupełnym nieporozumieniem byłoby utrudnianie korzystania z tego. Korzystanie z operatora [] jest wygodniejsze niż z metody .at, więc ludzie korzystają z [] narażając się na irytujące problemy czy wręcz niebezpieczeństwa. Sprytne wskaźniki to ceremonia w porównaniu np do jednego rodzaju referencji jak to jest w większości popularnych języków (Java, C#, JS, PHP, Python, etc). Sam kompilator C++ nie wymusza też reguł stosowania sprytnych wskaźników. Robi to natomiast kompilator Rusta, więc Rust ma tutaj przewagę nad C++em (jedną z wielu).

0
     *
    ***
   *****
  *******
 *********
***********
*(**(*wtf))
1

@WeiXiao: Karuzela śmiechu nie miała końca...

0

To nie jest żart. Jakoś nie jestem fanem dzikich zagniezdzonych gwiazdek w cpp

0

Cpp to fajny język
Ale poryty
Zasada najmniejszego zaskoczenia słabo tam obowiązuje

Dużo czasu schodzilo czasem ba szukanie odpowiedzi.a czemu tak

Poza tym miszmasz, np dużo typów stringów
Std. Cstring, char*, bstr i konweruj to (pisałem trochę w mfc)
To jak potem masz w Javie jeden string to po prostu ulga

Narzedzia
Testy jednostkowe w cpp to delikatna udreka w porównaniu z c#

Ogólnie cpp wymaga dużo więcej wysiłku, chyba za tym wysiłkiem nie idzie większą kasa
Może szacun, albo satysfakcja tyko

1

C++ nie jest trudne do ogarnięcia, ale po prostu tracisz dużo więcej czasu na rzeczy, które w innych językach zrobisz szybciej i prawdopodobnie lepiej, bo ktoś bardziej doświadczony dał Ci "podstawę".

Taaak, otwórz sobie efficent modern c++ choćby, C++ w połączeniu z stl i może być sporo różnych kombinacji i kruczków.

Ponadto, prosiłbym o jakieś dane potwierdzające tezę o tym, że smart pointery czy .at() przy std::array spowalnia program, bo mam inne informacje.

narzut na uniqu_ptr jest mały, na share_ptr już większy(pomijając już kwestie alokacji pamięci dla wskaźnika i problemów jakie mogą się zdarzyć), ba w appkach nastawionych na wydajność np. silniki gier wiem że rezygnuje się z shared_ptr na rzecz unique a i gołych wskaźników.

y.C++ z typi pierdołami typu smart pointery itd. jest mniej czytelny od Javy i wolniejszy od niej (wedlug tego co napisał @Wibowit). Ma więc wady C i wady Javy, zalet nie widze

ostatnio czytałem sobie jak to przechodzili z javy na c++ w jednym z backendów przy obsłudze baz danych bo zauważyli narzuty na jvm związane z GC(coś nie mogę tego artykułu znaleźć), i przy ilości transakcji jakie mieli robiło to różnicę. I udało się. Tyle że to nie musi być reguła. C++ bywa językiem trudnym z naleciałościami, nowymi wrzutkami które kompilują sprawy itd. Mimo to lata mijają a c++ nie chce odejść. Wiadomo jak człowiek siądzie przy C# to inny świat ale nie spisywałbym C++ na straty.

0

Śmiać mi się chce gdy czytam jak ktoś pisze, że c++ nie jest skomplikowany. 14 lat piszę w tym języku z czego 7 zawodowo w różnych firmach i nadal spotykam konstrukcje które wbijają mi klina. Nowe standardy wprowadzają dodatkowe mechanizmy i możliwości, które powodują że język nie staje się łatwiejszy do nauki.

Do zalet jego zastosowania na pewno dopisałbym możliwość wykonania pewnych operacji w czasie kompilacji co mądrze użyte pozwala na optymalizacje wykonania kodu - nie wiem jak to wyglada w innych technologiach.

0

Do zalet jego zastosowania na pewno dopisałbym możliwość wykonania pewnych operacji w czasie kompilacji co mądrze użyte pozwala na optymalizacje wykonania kodu - nie wiem jak to wyglada w innych technologiach.

Scala ma makra, które są w zasadzie wtyczkami do kompilatora, które piszesz tuż obok zwykłego kodu. Makra są kompilowane i odpalane w czasie kompilacji całości kodu, mają dostęp do stanu kompilatora i mogą go zmieniać, np generować klasy, metody, wstawiać wartości, itd

https://docs.scala-lang.org/overviews/macros/overview.html
http://scalamacros.org/
https://scalameta.org/

Rust ma derivable traits i macros, ale te są mniej zaawansowane niż makra w Scali

https://doc.rust-lang.org/book/second-edition/appendix-03-derivable-traits.html
https://doc.rust-lang.org/book/second-edition/appendix-04-macros.html

JVM natomiast ma leniwe ładowanie klas oraz konstrukcję invokedynamic służącą do generowania kodu ( za pomocą bootstrap methods) w czasie pierwszego wykonywania kodu. Przez to że jest to robione w czasie wykonywania to bootstrap methods mogą się zmieniać, a co za tym idzie kod który jest przez nie generowany może być coraz lepszy. Jednym z zastosowań invokedynamic jest klejenie stringów: http://openjdk.java.net/jeps/280 Inaczej mówiąc - w najnowszych wersjach Javy kod do klejenia stringów jest generowany w locie w momencie pierwszego jego wywołania. Dzięki temu boostrap methods do klejenia stringów (siedzące w JRE) mogą się polepszać (generować szybsze klejenie stringów) w nowych wersjach Javy, a bajtkodu (siedzącego w kodzie klienckim) wywołującego bootstrap methods za pomocą invokedynamic nie trzeba będzie zmieniać. Z punktu widzenia JVMa nie ma znaczenia czy bajtkod w aplikacji został w całości wygenerowany podczas kompilacji kodu Javowego, czy może częściowo jest generowany z użyciem invokedynamic. Dalej bajtkod dla klasy jest w całości ładowany i analizowany podczas ładowania klasy. Bootstrap methods tylko wydłużają ten proces. W Javie 11 wchodzi także początkowa wersja mechanizmu pozwalająca na generowanie stałych (należących do puli stałych w bajtkodzie) w czasie kompilacji: http://openjdk.java.net/jeps/309

0

Skoro C++ jest takie złe i nieprzyjemne w pisaniu to dlaczego jeszcze nie zrobiono nic lepszego ? niby jest Rust, ale niemal zerowe szanse, że Rust zastąpi C++ np. w branży gier klasy AAA.

1

No właśnie Rust jest lepszy :) Jak na razie niewiele jest napisane w Ruście, ale przecież to jeszcze młody język. Poza tym, skoro C++ jest tak bardzo lepszy od C (którego biblioteka standardowa jest toporna) to czemu np C nadal jest 2x popularniejsze od C++ na https://www.tiobe.com/tiobe-index/ ? Branża gier natomiast jest niespecjalnie dynamiczna, bo pisanie nowego dużego silnika gier od zera trwa latami, mimo tego iż zwykle przy pisaniu nowej wersji silnika w pewnym stopniu korzysta się (przynajmniej tymczasowo) z kodu starej wersji silnika. Pisanie nowej wersji silnika w Ruście oznaczałoby nie tylko całkowitą rezygnację z kodu w C++ (albo akrobacje podczas integracji C++ i Rusta), ale także konieczność przystosowania programistów (zarówno programistów silnika jak i programistów gier korzystających z tego silnika) do nowego języka.

Banki mają problemy z przepisywaniem projektów z COBOLa na Javę, a to chyba jest nawet mniej karkołomne niż przepisywanie z C++ na Rusta.

2

Jeżeli w jakimś programie wąskim gardłem jest alokowanie i zarządzanie pamięcią to znaczy że aplikacja jest bardzo źle napisana. W c++ można budować struktury pamięci, które same się zarządzają więcej tutaj : i narzut na GC znika.
Java w przeciętnym użyciu jest wolniejsza, ale czas wytwarzania kodu i error handling jest tam bardziej rozbudowany co ułatwia pisanie aplikacji przez przeciętnego programistę i sprawia że programowanie jest tańsze. Do tego wiele aplikacji biznesowych dobrze się skaluje więc po prostu dorzuci się kolejny node i po sprawie.
Inną kwestią są aplikacje które służą do obróbki zdjęć, grafiki, obliczeń FEM, tam najczęściej wąskim gardłem jest dostęp do pamięci i tutaj java mocno ssie, nie mamy takiego wpływu na to czy pamięć jest na stosie, stercie, w cache itp. do tego w tle pracuje GC które jeszcze potrafi wywłaszczać dostęp do pamięci.

Czyli dobiera się język do zadania, a nie na odwrót. Każdy z tych języków ma swoje mocne i słabe strony.

0

i narzut na GC znika.

A ja poraz N-ty powtórzę, że tracing GC jest sporo tańsze od atomic reference counting, a ARC jest potrzebne jeśli chcemy współdzielić dane między wątkami. W Javce współdzielenie niemutowalnych danych między wątkami jest banalne, bo wygląda tak samo i jest tak samo tanie jak korzystanie z niemutowalnych danych w jednym wątku.

Niech mi ktoś pokaże jak mogę w C++ w wydajny sposób korzystać z persistent data structures z wielu wątków jednocześnie.

Inną kwestią są aplikacje które służą do obróbki zdjęć, grafiki, obliczeń FEM, tam najczęściej wąskim gardłem jest dostęp do pamięci i tutaj java mocno ssie, nie mamy takiego wpływu na to czy pamięć jest na stosie, stercie, w cache itp. do tego w tle pracuje GC które jeszcze potrafi wywłaszczać dostęp do pamięci.

Na razie trochę ssie, ale idą zmiany:
http://openjdk.java.net/projects/valhalla/
http://openjdk.java.net/projects/panama/
http://openjdk.java.net/projects/shenandoah/
http://openjdk.java.net/projects/zgc/

Parę lat potrwa zanim te nowinki wejdą do oficjalnej wersji Javy, ale jak na razie Java i tak nadaje się do ogromnej ilości zastosowań.

0

Ja to cały czas czekam na @kq w tej dyskusji, sam jestem jeszcze zbyt mało wprawiony w c++, żeby podejmować tutaj jakieś rozległe dyskusje, a nie lubię strasznie podejścia "Nie wiem więc sie wypowiem". :D

0
Wibowit napisał(a):

i narzut na GC znika.

A ja poraz N-ty powtórzę, że tracing GC jest sporo tańsze od atomic reference counting, a ARC jest potrzebne jeśli chcemy współdzielić dane między wątkami. W Javce współdzielenie niemutowalnych danych między wątkami jest banalne, bo wygląda tak samo i jest tak samo tanie jak korzystanie z niemutowalnych danych w jednym wątku.

Tyle że Ty cały czas zakładasz że model pamięci dla Java i c++ będzie taki sam dla tego samego programu.
W c++ mogę tworzyć chwilowe obiekty na stosie co jest tanie i odwołanie się do tej pamięci jest szybsze niż odwołanie do obiektów na starcie. Nie ma w tej sytuacji też czegoś takiego jak zliczanie referencji. Wychodzę z metody i po prostu stos jest oczyszczony przez zmianę jednego inta w rejestrze procesora.
Dla wydajnego c++ trzeba przemyśleć model pamięci gdzie Javy to az tak bardzo nie interesuje.

Mały przykład automatycznego zarządzania alokacją, bez zliczania referencji.

#include <iostream>

using namespace std;

class DummyClass {
	public:
		DummyClass(int size) {
			cout<<"ALLOC MEM"<<endl;
			alloc = new char[size];
		}
		
		~DummyClass() {
			cout<<"CLEAR MEM"<<endl;
			delete[] alloc;
		}
	private:
		char* alloc;
		int size; 
};

void func1(DummyClass& dummyClass) {
	cout<<"FUNC 1"<<endl;
}

void func2(DummyClass& dummyClass) {
	cout<<"FUNC 2"<<endl;
}

void test() {
	DummyClass dummyAlloc(10 * 1024*1024);
	func1(dummyAlloc);
	func2(dummyAlloc);
}

int main(int argc, char** argv) {
	test();
	
	return 0;
}

Właścicielem alokacji stała sie metoda test(), wszystkie metody wywołane wewnątrz bezpiecznie korzystają z alokacji. Func1 i func2 można by było odpalić w wątkach dodając join na nich pod koniec metody test(). Zakończenie wątków kończyło by wywołanie metody i zagwarantowało zwolnienie pamięci.
Nie ma tu zliczania referencji, zero narzutu.

0

@xxx_xx_x:
Piszesz o czymś zupełnie innym. Ja pisałem o używaniu wskaźników jednowątkowo vs używaniu wskaźników wielowątkowo, a ty piszesz o alokowaniu na stercie vs alokowaniu na stosie.

Używanie wskaźników jednowątkowo vs używanie wskaźników wielowątkowo:

  • w Javce zwiększanie bądź zmniejszanie ilości referencji do obiektu wygląda tak samo, bo jest jeden rodzaj referencji (jest co prawda AtomicReference w Javie, ale to np nie jest potrzebne dla niemutowalnych struktur danych; ponadto z AtomicReference zwykle wyciąga zwykłą referencję i potem na tej zwykłej referencji operuje)
  • C++ wymaga źonglowania typami referencji, a jeśli chcemy mieć referencje do tego samego obiektu w różnych wątkach to musimy użyć atomowego zliczania referencji co jest kosztowne

Alokowanie na stercie vs alokowanie na stosie:

  • w C++ steruje się tym bezpośrednio ręcznie i kompilator nie ma tu nic do gadania (w sensie np nie zaalokuje na stercie, gdy programista chciał alokację na stosie)
  • w Javce mamy https://en.wikipedia.org/wiki/Escape_analysis więc programista pisze jakby alokował obiekty zawsze na stercie, ale JVM może niektóre alokacje zoptymalizować i przenieść na stos
0
Wibowit napisał(a):

@xxx_xx_x:
Piszesz o czymś zupełnie innym. Ja pisałem o używaniu wskaźników jednowątkowo vs używaniu wskaźników wielowątkowo, a ty piszesz o alokowaniu na stercie vs alokowaniu na stosie.

Używanie wskaźników jednowątkowo vs używanie wskaźników wielowątkowo:

  • w Javce zwiększanie bądź zmniejszanie ilości referencji do obiektu wygląda tak samo, bo jest jeden rodzaj referencji (jest co prawda AtomicReference w Javie, ale to np nie jest potrzebne dla niemutowalnych struktur danych; ponadto z AtomicReference zwykle wyciąga zwykłą referencję i potem na tej zwykłej referencji operuje)
  • C++ wymaga źonglowania typami referencji, a jeśli chcemy mieć referencje do tego samego obiektu w różnych wątkach to musimy użyć atomowego zliczania referencji co jest kosztowne

Alokowanie na stercie vs alokowanie na stosie:

  • w C++ steruje się tym bezpośrednio ręcznie i kompilator nie ma tu nic do gadania (w sensie np nie zaalokuje na stercie, gdy programista chciał alokację na stosie)
  • w Javce mamy https://en.wikipedia.org/wiki/Escape_analysis więc programista pisze jakby alokował obiekty zawsze na stercie, ale JVM może niektóre alokacje zoptymalizować i przenieść na stos

To ty nie rozumiesz, ja Ci cały czas powtarzam że w c++ inaczej rozwiązuje się podejście do pamięci i porównywanie wydajności poszczególnych elementów jest zwyczajnie głupie i niemiarodajne. Po co mam zliczać referencje wskaźników miedzy wątkami? Podałem ci prosty przykład, odpalam dwa wątki które współdzielą pamieć, ale nie są one nigdy jej właścicielami. Z konstrukcji kodu jasno wynika żywotność tej alokacji i nie ma potrzeby zliczania referencji....

0

Właścicielem alokacji stała sie metoda test(), wszystkie metody wywołane wewnątrz bezpiecznie korzystają z alokacji. Func1 i func2 można by było odpalić w wątkach dodając join na nich pod koniec metody test(). Zakończenie wątków kończyło by wywołanie metody i zagwarantowało zwolnienie pamięci.
Nie ma tu zliczania referencji, zero narzutu.

Dobra. To ja opiszę schemat używania trwałych struktur danych między wątkami. W Scali używa się takiego połączenia w zasadzie jako standard. Do wielowątkowości jest Akka (która wśród Scalowców jest tak popularna jak np Spring wśród Javowców), natomiast domyślne kolekcje w Scali to właśnie trwałe struktury danych. Domyślna Lista to niemutowalna lista pojedynczo wiązana. Lista ma mniej więcej taką definicję:

trait List
case class Cons(head: T, tail: List[T]) extends List
case object Nil extends List
object List {
  def apply[T](elems: T*): List[T] = ???
}

Dołożenie elementu na początku listy odbywa się w czasie stałym (zakładając że alokacja ma złożoność liniową od ilości alokowanych bajtów). Np:

val lista234 = List(2, 3, 4) // tutaj tworzymy listę z 3 elementów + Nil
val lista1234 = Cons(1, lista234) // tutaj tworzymy listę z 4 elementów, ale przez to że ogon w całości pokrywa się z poprzednią listą to tworzenie jej jest operacją O(1)

Teraz mogę referencję lista234 wysłać do wątku Wa, referencję lista1234 wysłać do wątku Wb, wątek Wa może zrobić podobny trik z listami i ogólnie mogę tak porozsyłać listy po 10 wątkach i te listy będą współdzielić dane.

Analogicznie to trwałych list działają np standardowe mapy w Scali. Zmiana mapy polega na utworzeniu nowej mapy z zaaplikowanymi zmianami - do starej jest oczywiście cały czas normalny dostęp. Dodanie elementu do mapy tworzy więc nową mapę i ma złożoność O(lg n), bo tylko jedna ścieżka w zrównoważonym drzewie się zmienia. Reszta poddrzew jest taka sam jak w starym drzewie (zakładając, że mapa jest oparta o drzewa), więc nie trzeba ich kopiować lub je w ogóle analizować. Mapy mogę oczywiście przesyłać między wątkami tak jak opisane powyżej listy.

Jak zaimplementować wydajnie coś takiego w C++?

PS:
Schemat który opisałem to standardowe wykorzystanie wielowątkowego programowania funkcyjnego. Zawsze jak jest napisane, że programowanie funkcyjne ułatwia programowanie wielowątkowe to chodzi o schematy analogiczne do opisanego przeze mnie w tym poście.

PS2:
W Javce biblioteką do programowania funkcyjnego jest np http://www.vavr.io/ - ma zestaw trwałych kolekcji i innych bajerów.

5

Jak dla mnie największym wrogiem C++ są uczelnie i kursy, cały czas uczące C++ z poprzedniego tysiąclecia. Nawet tutaj pierwszym strasznym słowem powiązanym z C++ były wskaźniki (choć to nie one są bezpośrednio problemem, a ich arytmetyka i losowe powiązanie do czasu życia zmiennych).

Dość zauważyć, że twórcy Javy wykorzystali ten negatywny PR i zmienili swoją nomenklaturę z wskaźników na referencje (ale jakimś cudem zapominając o NullPointerException).

W nowoczesnym C++ (czyli prawie od dekady już) nie ma żadnej potrzeby, aby wskaźników używać jako czegokolwiek innego niż obserwatorów (co zresztą pięknie pokazuje zalinkowana wyżej prelekcja Herba Suttera), cały ten cyrk z zarządzaniem pamięcią został już imo rozwiązany. Jeśli piszesz nowy kod - piszesz go bez problemów. Jak używasz starych bibliotek, to ich używasz zgodnie z ich API. W środku mogą mieć stary kod i ewentualne memleaki, ale to nie jest zadanie dla użytkownika biblioteki.

Co do ARC vs. GC: pewnie, GC jest często lepszy. Tylko z mojego doświadczenia (t.j. w branżach w których robiłem), był to zawsze problem bardzo mało znaczący. Jeśli chciałem przekazywać immutable dane wątkom, to przekazywałem je przez nagi wskaźnik/referencję, a czasem życia zarządzał odpowiedni manager (najczęściej po prostu dawałem wskaźnik do obiektu "na stosie", który znikał po joinie na wątkach), albo były to po prostu stałe globalne. Zdarzyło mi się kilka razy przekazywać shared_ptr z danymi między podsystemami, ale względem czasu ich życia, kilka atomowych operacji na refcountach wydaje się kompletnie bez znaczenia.

W każdym razie: poznać C++ na wysokim poziomie jest trudno, trzeba naprawdę się postarać. Nie znam żadnego innego języka na takim poziomie, więc nie mogę się z pewnością się wypowiadać, ale wydaje mi się, że inne języki też mają mnóstwo smaczków, o których trzeba wiedzieć; tylko może mają ich trochę mniej. Z drugiej strony, nie uważam, aby obecny C++ był zbyt trudny do użycia w zakresie niezbędnym do jego użycia w większości zastosowań.

Co do wydajności: uważam, że w większości zastosowań nie ma tak dużego znaczenia, jak się to przypisuje. Moja aplikacja zarządzająca w czasie rzeczywistym tysiącami pojemników w magazynie automatycznym, w którym pracowało kilkuset ludzi, a pojemnik mógł jechać po rolkach prawie 5 km zanim trafił gdzieś gdzie był, zajmowała ok 20MB RAM-u, i średnio 0% czasu pracy procesora, oferując czasy reakcji na poziomie 1ms. Nie optymalizowałem jej jakoś przesadnie, a dla ułatwienia kodowania wykorzystałem framework Qt. Jestem przekonany, że w Javie, C# lub D bym mógł osiągnąć podobny wynik (no, poza RAM-em), a największym ewentualnym problemem by była skalowalność i złożoność obliczeniowa zastosowanych algorytmów, a nie cokolwiek innego. Tak samo widzę to w prawie każdej innej branży poza dość specyficznymi wyjątkami (gamedev czy pisanie kerneli).

TL;DR: nie widzę powodu aby nowoczesnego C++ nie wybierać w projektach. Ale też nie widzę specjalnego powodu, aby go wybierać.

0

Co do ARC vs. GC: pewnie, GC jest często lepszy. Tylko z mojego doświadczenia (t.j. w branżach w których robiłem), był to zawsze problem bardzo mało znaczący. Jeśli chciałem przekazywać immutable dane wątkom, to przekazywałem je przez nagi wskaźnik/referencję, a czasem życia zarządzał odpowiedni manager (najczęściej po prostu dawałem wskaźnik do obiektu "na stosie", który znikał po joinie na wątkach), albo były to po prostu stałe globalne. Zdarzyło mi się kilka razy przekazywać shared_ptr z danymi między podsystemami, ale względem czasu ich życia, kilka atomowych operacji na refcountach wydaje się kompletnie bez znaczenia.

Siedzisz w C++ i nie widzisz trendów obecnych w innych językach. Wielowątkowe (albo i niekoniecznie wielowątkowe) programowanie funkcyjne powoli zdobywa popularność, a ludzie przekonują się, że trwałe struktury danych przynoszą korzyść w tworzeniu i rozwijaniu kodu. C++ do programowania funkcyjnego zupełnie nie pasuje.

0

Ale co ma programowanie funkcyjne (do którego C++ jak najbardziej się nadaje, btw), do ARC vs. GC?

0

Jak to co? Programowanie funkcyjne opiera się na trwałych strukturach danych, a takie świetnie się nadają do przetwarzania wielowątkowego. Oczywiście o ile masz tracing GC, a nie ARC.

1 użytkowników online, w tym zalogowanych: 0, gości: 1