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


14

Joel Spolsky ha definito il C ++ come "abbastanza corda per impiccarsi" . In realtà, stava riassumendo "C ++ efficace" di Scott Meyers:

È un libro che in pratica dice che il C ++ è abbastanza corda da appendere te stesso, e poi un paio di miglia in più di corda, e poi un paio di pillole suicide che si nascondono come M & M ...

Non ho una copia del libro, ma ci sono indicazioni che gran parte del libro si riferisce a insidie ​​nella gestione della memoria che sembrano essere rese discutibili 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 con un'attenta programmazione? In tal caso, fino a che punto e come vengono evitati?
  2. Ci sono nuove, diverse insidie ​​in C # di cui un nuovo programmatore C # dovrebbe essere a conoscenza? In tal caso, perché non potrebbero essere evitati dal design di C #?

10
Dalla FAQ : Your questions should be reasonably scoped. If you can imagine an entire book that answers your question, you’re asking too much.. Credo che ciò si qualifichi come una domanda del genere ...
Oded,

@Oded Ti riferisci alla domanda del titolo con caratteri limitati? O le mie 3+ domande più precise nel corpo del mio post?
alx9r,

3
Francamente, sia il titolo che ciascuna delle "domande più precise".
Oded,

3
Ho iniziato una discussione Meta su questa domanda.
Oded,

1
Per quanto riguarda la tua terza domanda ora cancellata, la serie C # efficace di Bill Wagner (ora 3 libri) mi ha insegnato più sulla programmazione di C # che su qualsiasi altra cosa che ho letto sull'argomento. La recensione di Martins su EC # ha ragione in quanto non può mai essere un sostituto diretto di C ++ efficace, ma ha torto a pensare che dovrebbe essere. Una volta che non devi più preoccuparti degli errori facili , devi passare a errori più difficili .
Mark Booth,

Risposte:


33

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

Non ha nulla a che fare con la gestione manuale della memoria. In entrambi i casi, questo è un problema risolto.

C / C ++:

In C ++, quando si commette un errore, il risultato non è definito.
Oppure, se si tenta di formulare determinati tipi di ipotesi sul sistema (ad esempio, overflow di numeri interi con segno), è probabile che il programma non sia definito.

Forse leggi questa serie in 3 parti sul comportamento indefinito.

Questo è ciò che rende C ++ così veloce: il compilatore non deve preoccuparsi di ciò che accade quando le cose vanno male, quindi può evitare di verificarne la correttezza.

C #, Java, ecc.

In C #, hai la garanzia che molti errori ti esploderanno in faccia come eccezioni, e sei garantito molto di più sul sistema sottostante.
Questa è una barriera fondamentale per rendere C # veloce come C ++, ma è anche una barriera fondamentale per rendere sicuro C ++ e rende C # più semplice da utilizzare e da eseguire nel debug.

Tutto il resto è solo sugo.


Tutte le cose non definite sono veramente definite dall'implementazione, quindi se overflow un numero intero senza segno in Visual Studio, otterrai un'eccezione se hai attivato i flag di compilazione corretti. Ora so che questo è ciò di cui stai parlando, ma non è un comportamento indefinito , è solo che la gente di solito non lo controlla. (lo stesso con un comportamento veramente indefinito come operator ++, è ben definito da ogni compilatore). Si potrebbe dire lo stesso con C #, c'è solo 1 implementazione - un sacco di "comportamento indefinito" se si esegue in Mono - ad es. bugzilla.xamarin.com/show_bug.cgi?id=310
gbjbaanb

1
È veramente definito o definito solo dall'attuale implementazione .net sull'attuale versione di Windows? Anche il comportamento indefinito in c ++ è completamente definito se lo si definisce come qualunque cosa faccia g ++.
Martin Beckett,

6
I numeri interi senza segno di overflow non sono affatto UB. E 'traboccante firmati numeri interi che è in UB.
DeadMG,

6
@gbjbaanb: Come ha detto DeadMG, l'overflow di numeri interi con segno non è definito. E ' non è definito dall'implementazione. Quelle frasi hanno significati specifici nello standard C ++ e non sono la stessa cosa. Non fare questo errore.
user541686,

1
@CharlesSalvia: Uh, in che modo esattamente "C ++ semplifica l'utilizzo della cache della CPU" rispetto a C #? E che tipo di controllo ti dà C ++ sulla memoria che non puoi avere in C #?
user541686,

12

C # evita le insidie ​​che vengono evitate in C ++ solo con un'attenta programmazione? In tal caso, fino a che punto e come vengono evitati?

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

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

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

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

  4. Stringhe - Il C ++ moderno lo rende un po 'migliore, ma char*è responsabile di circa il 95% di tutte le violazioni della sicurezza prima del 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 sta pregando che non hai bisogno del supporto Unicode.

E davvero, questa è la punta dell'iceberg. Il problema principale è che il C ++ è un linguaggio molto scarso per i principianti. E 'abbastanza incoerente, e molti dei vecchi davvero, davvero male insidie sono stati affrontati cambiando i modi di dire. Il problema è che i principianti devono quindi imparare i modi di dire da qualcosa come C ++ efficace. C # elimina del tutto molti di questi problemi e rende il resto meno problematico fino a quando non avanzi sul percorso di apprendimento.

Ci sono nuove, 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 #?

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

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

Un'altra semi-trappola che ho visto incontrare nei programmatori è la semantica di acquisizione 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 si pensa ingenuamente. Questo stampa 1010 volte.

Sono sicuro che ce ne sono molti altri che sto dimenticando, ma il problema principale è che sono meno pervasivi.


4
Le perdite di memoria appartengono al passato, e così è char*. Per non parlare del fatto che è ancora possibile perdere la memoria in C # bene.
DeadMG

2
Chiamare i template "glorified string incolling" è un po 'troppo. I modelli sono davvero una delle migliori caratteristiche di C ++.
Charles Salvia,

2
@CharlesSalvia Certo, sono la caratteristica distintiva del C ++. E sì, questa è forse una semplificazione eccessiva per l'impatto della compilazione. Ma hanno un impatto sproporzionato sui tempi di compilazione e sulle dimensioni dell'output, soprattutto se non stai attento.
Telastyn,

2
@deadMG certamente, anche se direi che molti dei trucchi di meta-programmazione dei template usati / necessari in C ++ sono ... meglio implementati tramite un meccanismo diverso.
Telastyn,

2
@Telastyn il punto centrale di type_traits è quello di ottenere informazioni sul tipo in fase di compilazione in modo da poter utilizzare queste informazioni per fare cose come modelli specializzati o funzioni di sovraccarico in modi specifici usandoenable_if
Charles Salvia

10

A mio avviso, i pericoli del C ++ sono in qualche modo esagerati.

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

Questa "corda extra", per così dire, di cui parlava Joel Spolsky , si riduce sostanzialmente a una cosa: scrivere classi che gestiscono internamente la propria memoria, nota anche come " Regola del 3 " (che ora può essere chiamata la Regola di 4 o regola di 5 in C ++ 11). Ciò significa che, se mai vuoi scrivere una classe che gestisca le proprie allocazioni di memoria internamente, devi sapere cosa stai facendo altrimenti il ​​tuo programma probabilmente andrà in crash. Devi creare con cura un costruttore, un costruttore di copia, un distruttore e un operatore di assegnazione, che è sorprendentemente facile sbagliare, causando spesso bizzarri arresti in fase di esecuzione.

TUTTAVIA , nella reale programmazione C ++ quotidiana, è davvero raro scrivere una classe che gestisca la propria memoria, quindi è fuorviante dire che i programmatori C ++ devono sempre essere "attenti" per evitare queste insidie. Di solito, farai solo 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 simile a ciò che faresti in Java o in C # - non richiede una gestione esplicita della memoria (perché la classe della libreria std::stringsi occupa di tutto ciò automaticamente) e non è richiesta alcuna roba "Regola di 3" poiché l'impostazione predefinita costruttore di copia e operatore di assegnazione va bene.

È solo quando provi a fare qualcosa del genere:

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 complicato per i principianti ottenere correttamente l'incarico, il distruttore e il costruttore di copie. Ma per la maggior parte dei casi, non c'è motivo di farlo. Il C ++ rende molto semplice evitare la gestione manuale della memoria il 99% delle volte usando classi di librerie come std::stringe std::vector.

Un altro problema correlato è la gestione manuale della memoria in un modo che non tiene conto della possibilità di generare un'eccezione. Piace:

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

Se some_function_which_may_throw()in realtà non un'eccezione, si è lasciato con una perdita di memoria, perché la memoria allocata per snon sarà mai recuperato. Ma di nuovo, in pratica, questo non è più un problema per lo stesso motivo per cui la "Regola del 3" non è più un problema. È molto raro (e di solito non necessario) gestire effettivamente la propria memoria con puntatori non elaborati. Per evitare il problema sopra, tutto ciò che dovresti fare è usare un std::stringo std::vector, e il distruttore verrebbe automaticamente invocato durante lo svolgimento dello stack dopo che l'eccezione è stata lanciata.

Quindi, un tema generale qui è che molte funzionalità di C ++ che non sono state ereditate da C, come l'inizializzazione / distruzione automatica, i costruttori di copie e le eccezioni, costringono un programmatore a prestare particolare attenzione durante la gestione manuale della memoria in C ++. Ma ancora una volta, questo è solo un problema se si intende in primo luogo eseguire la gestione manuale della memoria, che non è quasi mai più necessaria quando si hanno contenitori standard e puntatori intelligenti.

Quindi, secondo me, mentre il C ++ ti dà molta corda in più, non è quasi mai necessario usarlo per impiccarti, e le insidie ​​di cui parlava Joel sono banalmente facili da evitare nel C ++ moderno.


Era la regola dei tre in C ++ 03 ed è la regola dei quattro in C ++ 11 ora.
DeadMG

1
Potresti chiamarlo "Regola di 5" per costruttore di copia, spostamento costruttore, copia assegnazione, spostamento assegnazione e distruttore. Ma spostare la semantica non è sempre necessaria solo per una corretta gestione delle risorse.
Charles Salvia,

Non hai bisogno di spostare e copiare i compiti separatamente. Il linguaggio copia-e-scambia può fare entrambi gli operatori in uno.
DeadMG,

2
Risponde alla domanda Does C# avoid pitfalls that are avoided in C++ only by careful programming?. La risposta è "non proprio, perché è banalmente facile evitare le insidie ​​di cui Joel parlava nel moderno C ++"
Charles Salvia,

1
IMO, mentre linguaggi di alto livello come C # o Java ti forniscono la gestione della memoria e altre cose che dovrebbero aiutarti , non sempre fanno come previsto. Finisci ancora per occuparti della progettazione del tuo codice in modo da non lasciare perdite di memoria (che non è esattamente ciò che chiameresti in C ++). Dalla mia esperienza, trovo ANCORA più facile gestire la memoria in C ++ perché sai che verranno chiamati i distruttori e nella maggior parte dei casi eseguiranno la pulizia. Dopotutto, C ++ ha puntatori intelligenti per i casi in cui il design non consente una gestione efficiente della memoria. Il C ++ è fantastico ma non per i manichini.
Pijusn,

3

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

C # evita le insidie ​​che vengono evitate in C ++ solo con un'attenta programmazione? In tal caso, fino a che punto e come vengono evitati?

Non proprio. Regole come la Regola dei tre hanno perso enorme significato in C ++ 11 grazie unique_ptre shared_ptressendo standardizzate. L'uso delle classi Standard in modo vagamente sensato non è "codifica attenta", è "codifica di base". Inoltre, la percentuale della popolazione C ++ che è ancora sufficientemente stupida, disinformata o entrambe per fare cose come la gestione manuale della memoria è molto più bassa di prima. La realtà è che i docenti che desiderano dimostrare regole del genere devono passare settimane cercando di trovare esempi in cui si applicano ancora, perché le classi Standard coprono praticamente ogni caso d'uso immaginabile. Molte tecniche efficaci di C ++ sono andate allo stesso modo, come il dodo. Molti altri non sono specifici per C ++. Fammi vedere. Saltando il primo oggetto, i prossimi dieci sono:

  1. Non codificare C ++ come se fosse C. Questo è davvero solo buon senso.
  2. Limita le tue interfacce e usa l'incapsulamento. OOP.
  3. I masterizzatori di codice di inizializzazione in due fasi dovrebbero essere masterizzati sul rogo. OOP.
  4. Scopri quale valore ha la semantica. Questo è veramente specifico per C ++?
  5. Limita di nuovo le tue interfacce, questa volta in un modo leggermente diverso. OOP.
  6. Distruttori virtuali. Si. Probabilmente questo è ancora valido. finale overrideho contribuito a cambiare questo gioco in meglio. Crea il tuo distruttore overridee garantisci un bel errore del compilatore se erediti da qualcuno che non ha fatto il proprio distruttore virtual. Rendi la tua classe finale nessun povero scrub può venire avanti ed ereditarne accidentalmente senza un distruttore virtuale.
  7. Accadono cose brutte se le funzioni di pulizia falliscono. Questo non è proprio specifico per C ++ - puoi vedere gli stessi consigli sia per Java che per C # - e, beh, praticamente ogni lingua. Avere funzioni di pulizia che possono fallire è semplicemente negativo e non c'è nulla di C ++ o OOP su questo elemento.
  8. Sii consapevole di come l'ordine del costruttore influenza le funzioni virtuali. In modo esilarante, in Java (attuale o passato) chiamerebbe semplicemente erroneamente la funzione della classe Derived, che è anche peggio del comportamento di C ++. Indipendentemente da ciò, questo problema non è specifico per C ++.
  9. I sovraccarichi dell'operatore dovrebbero comportarsi come previsto dalle persone. Non proprio specifico. Inferno, non è nemmeno un sovraccarico dell'operatore specifico, lo stesso potrebbe essere applicato a qualsiasi funzione: non dargli un nome e poi fai in modo che qualcosa di completamente non intuitivo.
  10. Questo è attualmente considerato una cattiva pratica. Tutti gli operatori di assegnazione fortemente sicuri rispetto alle eccezioni affrontano bene l'auto-assegnazione, e l'auto-assegnazione è effettivamente un errore logico del programma e la verifica per l'auto-assegnazione non vale il costo delle prestazioni.

Ovviamente non esaminerò ogni singolo elemento C ++ efficace, ma la maggior parte di essi sta semplicemente applicando concetti di base al C ++. Troveresti lo stesso consiglio in qualsiasi linguaggio di operatore sovraccarico orientato agli oggetti e tipizzato in base al valore. I distruttori virtuali sono l'unico che è un trabocchetto C ++ ed è ancora finalvalido , anche se, probabilmente, con la classe di C ++ 11, non è così valido come lo era. Ricorda che C ++ efficace è stato scritto quando l'idea di applicare OOP, e le caratteristiche specifiche di C ++, era ancora molto nuova. Questi elementi non riguardano solo le insidie ​​di C ++ e altro su come affrontare il cambiamento da C e come usare OOP correttamente.

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 che puoi trovare ugualmente 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 usarlo sia considerato C ++ codice. Anche lo standard definisce goto, ma se dovessi scrivere un mucchio enorme di pasticcio di spaghetti usandolo, considererei il tuo problema, non quello della lingua. C'è una grande differenza tra "un'attenta codifica" e "Seguire gli idiomi di base della lingua".

Ci sono nuove, 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 #?

usingfa 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 l'enorme successo che i template erano in C ++, e hanno deciso che "Facciamo solo ereditare tutto da tutto e perdiamo tutta la sicurezza del nostro tipo" è stata la scelta più intelligente . Credo anche che puoi trovare alcune brutte sorprese in cose come le condizioni di gara con i delegati e altri divertimenti del genere. Poi ci sono altre cose generali, come il modo in cui i generici fanno schifo in modo orribile rispetto ai modelli, il posizionamento forzato davvero inutile di tutto in un class, e cose del genere.


5
Una base di utenti istruiti o nuovi costrutti non stanno davvero diminuendo la corda. Sono solo soluzioni, quindi meno persone finiscono per impiccarsi. Anche se questo è tutto un buon commento su C ++ efficace e il suo contesto nell'evoluzione del linguaggio.
Telastyn,

2
No. Si tratta di come un insieme di elementi in Effective C ++ siano concetti che potrebbero applicarsi ugualmente a qualsiasi linguaggio orientato agli oggetti con valori di valore. Ed educare la base di utenti a codificare il C ++ effettivo invece del C sta sicuramente diminuendo la corda che il C ++ ti offre. Inoltre, mi aspetto che i nuovi costrutti del linguaggio stiano diminuendo la corda. Si tratta di come solo perché lo standard C ++ definisce mallocnon significa che dovresti farlo, non più solo perché puoi puttana gotocome una cagna significa che è la corda con cui puoi appenderti.
DeadMG,

2
L'uso delle parti C di C ++ non è diverso dalla scrittura di tutto il codice unsafein C #, il che è altrettanto negativo. Potrei elencare ogni trappola della codifica C # come C, se lo desideri.
DeadMG,

@DeadMG: quindi davvero la domanda dovrebbe essere "un programmatore C ++ ha abbastanza corda per impiccarsi fintanto che è un programmatore C"
gbjbaanb

"Inoltre, la percentuale della popolazione C ++ che è ancora sufficientemente stupida, disinformata o entrambe per fare cose come la gestione manuale della memoria è molto più bassa di prima." Citazione necessaria.
dan04,

3

C # evita le insidie ​​che vengono evitate in C ++ solo con un'attenta programmazione? In tal caso, fino a che punto e come vengono evitati?

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 non elaborati) che sono sintatticamente convenienti ma ora considerati stile scadente.
  • Avere la semantica di riferimento invece della semantica di valore, il che rende discutibili almeno 10 degli elementi C ++ effettivi (ma introduce nuove insidie).
  • Avere meno comportamenti definiti dall'implementazione rispetto al C ++.
    • In particolare, in C ++ la codifica di caratteri di char, stringecc dipende dall'implementazione. Lo scisma tra l'approccio di Windows a Unicode ( wchar_tper UTF-16, charper "code page" obsolete) e l'approccio * nix (UTF-8) causa grandi difficoltà nel codice multipiattaforma. C #, OTOH, garantisce che a stringè UTF-16.

Ci sono nuove, diverse insidie ​​in C # di cui un nuovo programmatore C # dovrebbe essere a conoscenza?

Sì: IDisposable

Esiste un libro equivalente a "C ++ efficace" per C #?

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


0

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

C ++ è verificabile localmente . Posso ispezionare una singola classe in C ++ e determinare che la classe non perde la memoria o altre risorse, supponendo che tutte le classi di riferimento siano corrette. In Java o C #, è necessario controllare ogni classe di riferimento per determinare se richiede una 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.

3
... è piuttosto banale negli IDE moderni determinare se qualcosa eredita da IDisposable. Il problema principale è che devi sapere di usare auto_ptr(o alcuni dei suoi parenti). Questa è la proverbiale corda.
Telastyn,

2
@Telastyn no, il punto è che usi sempre un puntatore intelligente, a meno che tu non sappia davvero che non ne hai bisogno. In C # l'istruzione using è proprio come la corda a cui ti riferisci. (cioè in C ++ devi ricordare di usare un puntatore intelligente, perché allora C # non è così male anche se devi ricordare di usare sempre un'istruzione using)
gbjbaanb

1
@gbjbaanb Perché il cosa? Il 5% al ​​massimo delle classi C # è usa e getta? E sai che devi eliminarli se sono usa e getta. In C ++, ogni singolo oggetto è usa e getta. E non sai se la tua specifica istanza deve essere gestita. Cosa succede per i puntatori restituiti che non provengono da una fabbrica? È tua responsabilità pulirli? Non dovrebbe essere, ma a volte lo è. E ancora, solo perché dovresti sempre usare un puntatore intelligente non significa che l'opzione di non cessare di esistere. Soprattutto per i principianti, questa è una trappola significativa.
Telastyn,

2
@Telastyn: Sapere di usare auto_ptrè semplice come sapere di usare IEnumerableo sapere di usare le interfacce, o non usare il virgola mobile per valuta o simili. È un'applicazione base di DRY. Nessuno che conosca le basi di come programmare farebbe quell'errore. A differenza di using. Il problema usingè che devi sapere per ogni classe se è o meno usa e getta (e spero che non cambi mai e poi mai), e se non è usa e getta, bandisci automaticamente tutte le classi derivate che potrebbero essere usa e getta.
DeadMG,

2
Kevin: La tua risposta non ha senso. Non è colpa di C # che tu stia sbagliando. Si fa a NON dipendono da finalizzatori in scritto correttamente il codice C # . Se hai un campo che ha un Disposemetodo, devi implementarlo IDisposable(il modo "corretto"). Se la tua classe lo fa (che è l'equivalente dell'implementazione di RAII per la tua classe in C ++) e tu usi using(che è come i puntatori intelligenti in C ++), tutto funziona perfettamente. Il finalizzatore ha principalmente lo scopo di prevenire incidenti: Disposeè responsabile della correttezza e, se non lo si utilizza, beh, è ​​colpa tua, non di C #.
user541686,

0

Sì, 100% sì, poiché penso che sia impossibile liberare memoria e usarlo in C # (supponendo che sia gestito e non si passa in modalità non sicura).

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

Per quanto riguarda errori di battitura, errori sciocchi ed ecc. (Es: if (i=0)bc la chiave si è bloccata quando si preme == molto rapidamente) il compilatore si lamenta che è bello in quanto migliora la qualità del codice. Un altro esempio è dimenticare le breakistruzioni switch e non consentire di dichiarare variabili statiche in una funzione (che a volte non mi piace ma è una buona idea imo).


4
Java e C # fatto la =/ ==problema ancora peggiore, utilizzando ==per l'uguaglianza di riferimento e l'introduzione .equalsper l'uguaglianza di valore. Il programmatore scadente ora deve tenere traccia del fatto che una variabile sia "doppia" o "doppia" e assicurarsi di chiamare la variante giusta.
Kevin Cline,

@kevincline +1 ma in C # structpuoi fare ciò ==che funziona incredibilmente bene poiché la maggior parte delle volte si hanno solo stringhe, ints e float (cioè solo membri di struct). Nel mio codice non ho mai avuto quel problema tranne quando voglio confrontare le matrici. Non credo di aver mai confrontato l'elenco o i tipi non struct (stringa, int, float, DateTime, KeyValuePair e molti altri)

2
Python ha capito bene usando l' ==uguaglianza di valore e l'uguaglianza isdi riferimento.
dan04,

@ dan04 - Quanti tipi di uguaglianza pensi abbia C #? Guarda l'eccellente discorso sui fulmini ACCU: alcuni oggetti sono più uguali di altri
Mark Booth,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.