Storia della questione:
C++ è stato costruito sulla base di C, che forniva un insieme ridotto di tipi primitivi: numeri, caratteri, array. Con l'evoluzione del linguaggio sono stati introdotti concetti come strutture, classi, enumerazioni — questi sono diventati tipi definiti dall'utente.
Problema:
Il tipo di dati in un programma determina quanto spazio di memoria occuperà una variabile, come sarà inizializzata, copiata, distrutta, confrontata. I tipi primitivi hanno un comportamento definito dallo standard, mentre i tipi definiti dall'utente richiedono una descrizione esplicita di tutti gli aspetti. Errori nella gestione dei tipi definiti dall'utente possono portare a crash, perdite di memoria, confronti scorretti tra oggetti, ecc.
Soluzione:
I tipi primitivi includono int, float, double, char, bool e altri "primitivi". I tipi definiti dall'utente possono essere qualsiasi struttura, classe, unione creata da te. Per compiti complessi è necessario implementare i tipi definiti dall'utente con una semantica di copia, confronto e gestione delle risorse corretta.
Esempio di codice:
// Tipo primitivo int x = 5; // Tipo definito dall'utente struct Point { double x, y; }; Point a = {1.0, 2.0}; // Classe con risorse class MyFile { public: MyFile(const std::string& fn) : f(fopen(fn.c_str(), "r")) {} ~MyFile() { if (f) fclose(f); } private: FILE* f; };
Possono i tipi definiti dall'utente comportarsi completamente come tipi primitivi (ad esempio, essere confrontati con == "out of the box")?
No, il confronto == funziona per impostazione predefinita solo a partire da C++20 tramite defaulted operator==, prima era necessaria una definizione esplicita.
È possibile non implementare il costruttore di copia e il distruttore se la classe mantiene solo un puntatore raw (int, FILE, ecc.)?**
No, in questo caso la copia per impostazione predefinita sarà "superficiale", il che porterà a perdite di memoria o a un rilascio accidentale di memoria/risorse. È necessario implementare il "Rule of Five".
Il valore di una struttura sarà inizializzato a zero per impostazione predefinita (Point p;)?
No, una struttura locale può non essere inizializzata, il contenuto della memoria è casuale. Utilizzare un'inizializzazione esplicita.
Caso negativo:
Una classe wrapper per un file non ha implementato il distruttore. Il programma funzionava finché non ha iniziato a gestire migliaia di file senza chiudere i descrittori.
Vantaggi: meno codice, aspetto semplificato Svantaggi: perdite di risorse, comportamento instabile
Caso positivo:
La classe implementa RAII — il file si apre nel costruttore, si chiude nel distruttore, il copia è vietata.
Vantaggi: affidabilità, sicurezza, interfaccia pulita Svantaggi: necessità di ricordare la "regola dei cinque" e scrivere più codice