C # ti dà "meno corda per impiccarti" rispetto al C ++? [chiuso]

14

Joel Spolsky ha caratterizzato il C ++ come "abbastanza corda per impiccarti" . In realtà, riassumeva "Effective C ++" di Scott Meyers:

It's a book that basically says, C++ is enough rope to hang yourself, and then a couple of extra miles of rope, and then a couple of suicide pills that are disguised as M&Ms...

Non ho una copia del libro, ma ci sono indicazioni che gran parte del libro riguarda le insidie nella gestione della memoria che sembrano essere resi moot in C # perché il runtime gestisce questi problemi per te.

Ecco le mie domande:

  1. C # evita le insidie che vengono evitate in C ++ solo da un'attenta programmazione? Se sì, in quale misura e in che modo vengono evitati?
  2. Ci sono nuove e diverse insidie in C # di cui un nuovo programmatore C # dovrebbe essere a conoscenza? Se è così, perché non potrebbero essere evitati dal design di C #?
posta alx9r 25.08.2012 - 22:23
fonte

7 risposte

33

La fondamentale differenza tra C ++ e C # deriva da comportamento indefinito .

Ha niente da fare con la gestione manuale della memoria. In entrambi i casi, si tratta di un problema risolto.

C / C ++:

In C ++, quando commetti un errore, il risultato non è definito.
Oppure, se provi a fare alcuni tipi di ipotesi sul sistema (ad esempio overflow di interi con segno), è probabile che il tuo programma non sia definito.

Forse leggi questa serie in 3 parti su undefined comportamento.

Questo è ciò che rende C ++ così veloce - il compilatore non deve preoccuparsi di cosa succede quando le cose vanno male, così può evitare di controllare la correttezza.

C #, Java, ecc.

In C #, sei garantito che molti errori si faranno esplodere in faccia come eccezioni e ti verrà garantito molto di più sul sistema sottostante. < br> Questa è una barriera fondamentale per rendere C # veloce come C ++, ma è anche una barriera fondamentale per rendere C ++ sicuro, e rende C # più facile da lavorare e da eseguire il debug.

Tutto il resto è solo salsa.

    
risposta data 26.08.2012 - 00:16
fonte
12

Does C# avoid pitfalls that are avoided in C++ only by careful programming? If so, to what degree and how are they avoided?

La maggior parte lo fa, altri no. E, naturalmente, ne fa di nuovi.

  1. Comportamento indefinito - La più grande trappola con C ++ è che c'è un sacco di linguaggio non definito. Il compilatore può letteralmente far saltare in aria l'universo quando fai queste cose, e andrà tutto bene. Naturalmente, questo è raro, ma è abbastanza comune perché il tuo programma funzioni bene su una macchina e per davvero nessuna buona ragione non funziona su un'altra macchina. O peggio, agisci in modo leggermente diverso. C # ha alcuni casi di comportamento non definito nelle sue specifiche, ma sono rari e in aree del linguaggio che sono raramente percorse. C ++ ha la possibilità di imbattersi in un comportamento indefinito ogni volta che fai una dichiarazione.

  2. Perdite di memoria - Questo è meno preoccupante per il C ++ moderno, ma per i principianti e per circa metà della sua durata, C ++ ha reso super facile la perdita di memoria. Efficace C ++ ha seguito l'evoluzione delle pratiche per eliminare questa preoccupazione. Detto questo, C # può perdere ancora memoria. Il caso più comune in cui si imbattono è l'acquisizione di eventi. Se hai un oggetto e metti uno dei suoi metodi come gestore di un evento, il proprietario di quell'evento deve essere GC'd affinché l'oggetto muoia. La maggior parte dei principianti non si rende conto che il gestore di eventi conta come riferimento. Ci sono anche problemi con la mancata disponibilità di risorse usa e getta che possono perdere memoria, ma non sono così comuni come puntatori in C ++ pre-efficace.

  3. Compilation - C ++ ha un modello di compilazione ritardato. Questo porta a una serie di trucchi per giocare bene con esso, e mantenere i tempi di compilazione.

  4. Stringhe - Il C ++ moderno rende questo un po 'meglio, ma char* è responsabile del ~ 95% di tutte le violazioni della sicurezza prima dell'anno 2000. Per i programmatori esperti, si concentreranno su std::string , ma è ancora qualcosa da evitare e un problema nelle librerie più vecchie / peggiori. E questo è pregare che tu non abbia bisogno del supporto Unicode.

E davvero, questa è la punta dell'iceberg. Il problema principale è che il C ++ è un linguaggio molto scarso per i principianti. È piuttosto incoerente, e molte delle vecchie vere e proprie veramente trappole sbagliate sono state gestite cambiando gli idiomi. Il problema è che i principianti devono quindi imparare gli idiomi da qualcosa come Effective C ++. C # elimina del tutto molti di questi problemi e rende il resto meno preoccupante finché non si prosegue lungo il percorso di apprendimento.

Are there new, different pitfalls in C# that a new C# programmer should be aware of? If so, why couldn't theybe avoided by the design of C#?

Ho menzionato il problema dell'evento "perdita di memoria". Questo non è un problema linguistico tanto quanto il programmatore si aspetta qualcosa che il linguaggio non può fare.

Un altro è che il finalizzatore per un oggetto C # non è tecnicamente garantito per essere eseguito dal runtime. Questo non è di solito importante, ma fa sì che alcune cose siano progettate in modo diverso da come ci si potrebbe aspettare.

Un'altra semi-trappola in cui ho incontrato i programmatori è la semantica di cattura di funzioni anonime. Quando acquisisci una variabile, acquisisci la variabile . Esempio:

List<Action> actions = new List<Action>();
for(int x = 0; x < 10; ++x ){
    actions.Add(() => Console.WriteLine(x));
}

foreach(var action in actions){
    action();
}

Non fa ciò che è ingenuo è pensato. Questo stampa 10 10 volte.

Sono sicuro che ci sono un certo numero di altri che sto dimenticando, ma il problema principale è che sono meno pervasivi.

    
risposta data 26.08.2012 - 00:47
fonte
10

Secondo me, i pericoli del C ++ sono alquanto esagerati.

Il pericolo essenziale è questo: mentre C # ti permette di eseguire operazioni di puntatore "non sicure" usando la parola chiave unsafe , C ++ (essendo principalmente un superset di C) ti permetterà di usare i puntatori ogni volta che ne hai voglia. Oltre ai soliti pericoli inerenti all'uso dei puntatori (che sono gli stessi di C), come perdite di memoria, overflow del buffer, puntatori penzolanti, ecc., Il C ++ introduce nuovi modi per rovinare seriamente le cose.

Questa "corda extra", per così dire, che Joel Spolsky parlava , fondamentalmente si riduce a una cosa: scrivere classi che gestiscono internamente la propria memoria, noto anche come " Regola di 3 "(che ora può essere chiamato Regola di 4 o Regola di 5 in C ++ 11). Questo significa che se vuoi scrivere una classe che gestisce internamente le proprie allocazioni di memoria, devi sapere cosa stai facendo, altrimenti il tuo programma andrà in crash. Devi creare con cura un costruttore, un costruttore di copia, un distruttore e un operatore di assegnazione, il che è sorprendentemente facile da sbagliare, spesso causa di bizzarri arresti anomali in fase di runtime.

TUTTAVIA , nella programmazione C ++ attuale ogni giorno, è molto raro infatti scrivere una classe che gestisce la propria memoria, quindi è fuorviante dire che i programmatori C ++ sempre Bisogna essere "attenti" per evitare queste insidie. Di solito, farai qualcosa di più simile a:

class Foo
{
    public:

    Foo(const std::string& s) 
        : m_first_name(s)
    { }

    private:

    std::string m_first_name;
};

Questa classe sembra molto vicina a ciò che faresti in Java o C # - non richiede una gestione esplicita della memoria (perché la classe della libreria std::string si occupa di tutto ciò automaticamente), e nessuna "regola di 3" è roba richiesto a tutti dal momento che il costruttore di copie e l'operatore di assegnazione predefiniti vanno bene.

È solo quando provi a fare qualcosa del tipo:

class Foo
{
    public:

    Foo(const char* s)
    { 
        std::size_t len = std::strlen(s);
        m_name = new char[len + 1];
        std::strcpy(m_name, s);
    }

    Foo(const Foo& f); // must implement proper copy constructor

    Foo& operator = (const Foo& f); // must implement proper assignment operator

    ~Foo(); // must free resource in destructor

    private:

    char* m_name;
};

In questo caso, può essere difficile per i principianti ottenere il compito, il distruttore e il costruttore di copie corretti. Ma per la maggior parte dei casi, non c'è motivo di farlo mai. C ++ rende molto facile evitare la gestione manuale della memoria il 99% delle volte utilizzando classi di libreria come std::string e std::vector .

Un altro problema correlato è la gestione manuale della memoria in un modo che non tiene conto della possibilità che venga lanciata un'eccezione. Come:

char* s = new char[100];
some_function_which_may_throw();
/* ... */
delete[] s;

Se some_function_which_may_throw() effettivamente fa genera un'eccezione, ti rimane una perdita di memoria perché la memoria allocata per s non sarà mai recuperata. Ma ancora una volta, in pratica, questo non è più un problema per la stessa ragione per cui la "Regola di 3" non è più un gran problema. È molto raro (e di solito non necessario) gestire effettivamente la tua memoria con puntatori grezzi. Per evitare il problema precedente, tutto ciò che devi fare è usare std::string o std::vector , e il distruttore verrà automaticamente richiamato durante lo srotolamento dello stack dopo che è stata lanciata l'eccezione.

Quindi, un tema generale qui è che molte delle caratteristiche del C ++ che erano non ereditate da C, come l'inizializzazione / distruzione automatica, i costruttori di copia e le eccezioni, costringono un programmatore a prestare la massima attenzione quando fa gestione manuale della memoria in C ++. Ma ancora una volta, questo è solo un problema se si intende fare prima la gestione manuale della memoria, che non è quasi mai più necessaria quando si dispone di contenitori standard e puntatori intelligenti.

Quindi, secondo me, mentre C ++ ti dà un sacco di corda extra, non è quasi mai necessario usarlo per appendere te stesso, e le insidie di cui parlava Joel sono banalmente facili da evitare nel moderno C ++.

    
risposta data 25.08.2012 - 23:55
fonte
3

Non sarei davvero d'accordo. Forse meno insidie di C ++ come esisteva nel 1985.

Does C# avoid pitfalls that are avoided in C++ only by careful programming? If so, to what degree and how are they avoided?

Non proprio. Regole come la Regola del Tre hanno perso enorme importanza in C ++ 11 grazie a unique_ptr e shared_ptr come Standard. Usare le classi standard in un modo vagamente ragionevole non è una "attenta codifica", è una "codifica di base". Inoltre, la percentuale della popolazione C ++ che è ancora sufficientemente stupida, disinformata o entrambe le cose da fare come la gestione manuale della memoria è molto più bassa di prima. La realtà è che i docenti che desiderano dimostrare regole del genere devono trascorrere settimane a cercare esempi in cui si applicano ancora, perché le classi standard coprono praticamente ogni caso d'uso immaginabile. Molte efficaci tecniche C ++ sono andate allo stesso modo: la via del dodo. Molti degli altri non sono proprio specifici per C ++. Fammi vedere. Saltando il primo oggetto, i successivi dieci sono:

  1. Non codificare C ++ come se fosse C. Questo è davvero solo buon senso.
  2. Limita le tue interfacce e utilizza l'incapsulamento. OOP.
  3. Gli autori di codice di inizializzazione bifase devono essere masterizzati sul rogo. OOP.
  4. Conoscenza della semantica del valore. Questo è veramente specifico per C ++?
  5. Limita nuovamente le interfacce, questa volta in modo leggermente diverso. OOP.
  6. Distruttori virtuali. Si. Questo probabilmente è ancora valido, in qualche modo. final e override hanno aiutato a cambiare questo particolare gioco in meglio. Crea il tuo distruttore override e garantisci un bel errore del compilatore se erediti da qualcuno che non ha reso il loro distruttore virtual . Rendi la tua classe final e nessuna cattiva macchia può venire e ereditarla accidentalmente senza un distruttore virtuale.
  7. Le cose brutte si verificano se le funzioni di pulizia falliscono. Questo non è proprio specifico per C ++ - puoi vedere lo stesso consiglio sia per Java che per C # - e, beh, praticamente per ogni lingua. Avere funzioni di pulizia che possono fallire è semplicemente sbagliato e non c'è nulla di C ++ o di OOP su questo elemento.
  8. Sii consapevole di come l'ordine del costruttore influenza le funzioni virtuali. Esilarante, in Java (corrente o passato) chiamerebbe in modo errato la funzione della classe derivata, che è anche peggiore del comportamento di C ++. Indipendentemente da ciò, questo problema non è specifico di C ++.
  9. I sovraccarichi dell'operatore dovrebbero comportarsi come le persone si aspettano. Non proprio specifico. Inferno, non è nemmeno un sovraccarico specifico dell'operatore, lo stesso potrebbe essere applicato a qualsiasi funzione - non dargli un nome e poi fargli fare qualcosa di completamente non intuitivo.
  10. Questo è attualmente considerato una cattiva pratica. Tutti gli operatori di assegnamento con sicurezza elevata, che si occupano di autoassegnazione, e l'autoassegnazione è effettivamente un errore logico del programma, e il controllo dell'assegnazione di sé non vale il costo delle prestazioni.

Ovviamente non ho intenzione di esaminare ogni singolo oggetto Effective C ++, ma la maggior parte di loro sta semplicemente applicando concetti di base a C ++. Si troverà lo stesso consiglio in qualsiasi linguaggio di operatore di overloadable orientato agli oggetti value-typed. I distruttori virtuali sono l'unico che è un trabocchetto del C ++ ed è ancora valido, anche se, discutibilmente, con la classe final di C ++ 11, non è così valido come era. Ricorda che Effective C ++ è stato scritto quando l'idea di applicare OOP e le funzionalità specifiche di C ++ era ancora molto nuova. Questi elementi difficilmente descrivono le insidie del C ++ e altro su come affrontare il cambiamento da C e su come utilizzare correttamente OOP.

Modifica: le insidie del C ++ non includono cose come le insidie di malloc . Voglio dire, per uno, ogni singola trappola che puoi trovare nel codice C puoi ugualmente trovare nel codice C # non sicuro, quindi non è particolarmente rilevante, e in secondo luogo, solo perché lo standard lo definisce per l'interoperabilità non significa che il suo utilizzo sia considerato C ++ codice. Lo standard definisce anche goto , ma se dovessi scrivere un gigantesco mucchio di spaghetti che lo usano, considererei il tuo problema, non quello della lingua. C'è una grande differenza tra "attenta codifica" e "Seguire gli idiomi fondamentali della lingua".

Are there new, different pitfalls in C# that a new C# programmer should be aware of? If so, why couldn't theybe avoided by the design of C#?

using fa schifo. Lo fa davvero. E non ho idea del perché non sia stato fatto qualcosa di meglio. Inoltre, Base[] = Derived[] e praticamente ogni uso di Object, che esiste perché i progettisti originali non hanno notato il successo enorme dei modelli in C ++ e hanno deciso che "Tutto è ereditato da tutto e perdiamo tutta la sicurezza del nostro tipo" è stato la scelta più intelligente. Credo anche che tu possa trovare alcune brutte sorprese in cose come le condizioni di gara con i delegati e altri divertimenti simili. Poi ci sono altre cose generali, come il modo in cui i generici succhiano orribilmente in confronto ai template, la collocazione forzata davvero superflua di tutto in un class , e cose del genere.

    
risposta data 26.08.2012 - 02:01
fonte
3

Does C# avoid pitfalls that are avoided in C++ only by careful programming? If so, to what degree and how are they avoided?

C # ha i vantaggi di:

  • Non essendo retrocompatibile con C, evitando così di avere una lunga lista di caratteristiche del linguaggio "malvagio" (ad esempio, puntatori grezzi) che sono sintatticamente convenienti ma ora considerati di cattivo gusto.
  • Avere riferimento alla semantica anziché alla semantica del valore, il che rende almeno 10 degli elementi Effective C ++ (ma introduce nuove insidie).
  • Avere meno comportamenti definiti dall'implementazione rispetto al C ++.
    • In particolare, in C ++ la codifica dei caratteri di char , string , ecc. è definita dall'implementazione. Lo scisma tra l'approccio di Windows a Unicode ( wchar_t per UTF-16, char per "code page" obsolete) e l'approccio * nix (UTF-8) causa grandi difficoltà nel codice multipiattaforma. C #, OTOH, garantisce che string sia UTF-16.

Are there new, different pitfalls in C# that a new C# programmer should be aware of?

Sì: IDisposable

Is there an equivalent book to "Effective C++" for C#?

C'è un libro chiamato C # efficace che è simile nella struttura a Effettivo C ++ .

    
risposta data 27.08.2012 - 10:06
fonte
0

No, C # (e Java) sono meno sicuri di C ++

C ++ è localmente verificabile . Posso ispezionare una singola classe in C ++ e determinare che la classe non perde memoria o altre risorse, assumendo che tutte le classi di riferimento siano corrette. In Java o C #, è necessario controllare ogni classe di riferimento per determinare se richiede la finalizzazione di qualche tipo.

C ++:

{
   some_resource r(...);  // resource initialized
   ...
}  // resource destructor called, no leaks here

C #:

{
   SomeResource r = new SomeResource(...); // resource initialized
   ...
} // did I need to finalize that?  May I should have used 'using' 
  // (or in Java, a grotesque try/finally construct)?  No way to tell
  // without checking the documentation for SomeResource

C ++:

{
    auto_ptr<SomeInterface> i = SomeFactory.create(...);
    i->f(...);
} // automatic finalization and memory release.  A new implementation of
  // SomeInterface can allocate and free resources with no impact
  // on existing code

C #:

{
   SomeInterface i = SomeFactory.create(...);
   i.f(...);
   ...
} // Sure hope someone didn't create an implementation of SomeInterface
  // that requires finalization.  In C# and Java it is necessary to decide whether
  // any implementation could require finalization when the interface is defined.
  // If the initial decision is 'no finalization', then no future implementation  
  // can acquire any resource without creating potential leaks in existing code.
    
risposta data 26.08.2012 - 06:56
fonte
0

Sì 100% sì, perché penso che sia impossibile liberare memoria e usarla in C # (supponendo che sia gestita e che tu non entri in modalità non sicura).

Ma se sai come programmare in C ++ quale non è un numero incredibile di persone. Stai abbastanza bene. Come le classi di Charles Salvia non gestiscono realmente i loro ricordi poiché tutto viene gestito in classi STL preesistenti. Uso raramente dei puntatori. In effetti sono andato progetti senza usare un singolo puntatore. (C ++ 11 lo rende più semplice).

Per fare errori di battitura, errori stupidi ed ecc. (es: if (i=0) bc il tasto si è bloccato quando si preme == molto velocemente) il compilatore si lamenta che è bello in quanto migliora la qualità del codice. Un altro esempio sta dimenticando break nelle istruzioni switch e non ti permette di dichiarare variabili statiche in una funzione (che a volte non mi piace, ma è una buona idea imo).

    
risposta data 26.08.2012 - 05:43
fonte

Leggi altre domande sui tag