Constructori, destructori și regula 0/3/5
Stăpânește întregul ciclu de viață al obiectelor C++ – de la construcție până la distrugere – și învață când să scrii (sau să nu scrii) funcții membre speciale.
Ciclul de viață al obiectului în C++
În C++, aveți control direct asupra momentului și modului în care obiectele sunt create și distruse. Compilatorul poate genera automat până la șase funcții membre speciale: constructor implicit, constructor de copiere, operator de atribuire de copiere, constructor de mutare, operator de atribuire de mutare și destructor. Înțelegerea când să-ți scrii propriul – și când să lași compilatorul să o facă – este una dintre cele mai importante abilități din C++.
Soiuri de constructori
Constructorii inițializează un obiect. C++ acceptă mai multe tipuri: implicit (fără argumente), parametrat, conversie (un singur argument, conversie implicită) și delegare (un constructor îl apelează pe altul).
#include <iostream>
#include <string>
class Temperature {
double celsius_;
std::string label_;
public:
// Default constructor
Temperature() : celsius_{0.0}, label_{"unknown"} {}
// Parameterized constructor
Temperature(double c, const std::string& label)
: celsius_{c}, label_{label} {}
// Converting constructor — allows implicit conversion from double
Temperature(double c) : celsius_{c}, label_{"sensor"} {}
// Delegating constructor — reuses another constructor
Temperature(const std::string& label)
: Temperature{0.0, label} {} // delegates to parameterized
void print() const {
std::cout << label_ << ": " << celsius_ << "°C\n";
}
};
int main() {
Temperature t1; // default
Temperature t2{36.6, "body"}; // parameterized
Temperature t3 = 100.0; // converting (implicit)
Temperature t4{"outdoor"}; // delegating
t1.print(); // unknown: 0°C
t2.print(); // body: 36.6°C
t3.print(); // sensor: 100°C
t4.print(); // outdoor: 0°C
return 0;
}Cuvântul cheie explicit
Constructorii cu un singur argument permit conversiile implicite, care pot provoca erori subtile. Utilizați explicit pentru a preveni conversiile neintenționate - aceasta este aproape întotdeauna ceea ce doriți.
#include <iostream>
#include <string>
class UserId {
int id_;
public:
explicit UserId(int id) : id_{id} {}
int get() const { return id_; }
};
void process(UserId uid) {
std::cout << "Processing user #" << uid.get() << '\n';
}
int main() {
UserId u1{42}; // OK: direct initialization
// UserId u2 = 42; // ERROR: implicit conversion blocked by explicit
// process(42); // ERROR: would require implicit conversion
process(UserId{42}); // OK: explicit construction
return 0;
}Listele de inițializare a membrilor și ordinea de inițializare
Lista de inițializare a membrilor este modalitatea preferată de a inițializa membrii. Este obligatoriu pentru membriiconst, membrii de referință și membrii fără un constructor implicit. Membrii sunt inițializați în ordinea declarației, nu în ordinea în care apar în lista de inițializare.
#include <iostream>
#include <string>
class Connection {
const int max_retries_; // const: MUST use init list
std::string& log_target_; // reference: MUST use init list
std::string host_;
int port_;
public:
Connection(std::string& log, const std::string& host, int port)
: max_retries_{3} // const member
, log_target_{log} // reference member
, host_{host} // initialized in declaration order
, port_{port}
{
// Constructor body runs AFTER all members are initialized
log_target_ += "[Connection to " + host_ + " created]\n";
}
void print() const {
std::cout << host_ << ':' << port_
<< " (max retries: " << max_retries_ << ")\n";
}
};
int main() {
std::string log;
Connection conn{log, "db.example.com", 5432};
conn.print();
std::cout << log;
return 0;
}Destructori și regula lui Zero / Trei / Cinci
Un destructor (~ClassName()) rulează atunci când un obiect este distrus - eliberează resurse și nu trebuie să arunce excepții. Pentru majoritatea claselor, destructorul generat de compilator este suficient.
Regula lui Zero: preferați pachetele RAII (std::string,std::vector,std::unique_ptr), astfel încât să nu aveți nevoie să scrieți nicio funcție specială pentru membri. Dacă trebuie să gestionați o resursă brută, Regula de trei spune că aveți nevoie de un destructor, un constructor de copiere și un operator de atribuire de copiere. Regula celor cinci extinde acest lucru pentru a include constructorul de mutare și operatorul de atribuire a mutarii.
#include <iostream>
#include <cstring>
// === Rule of Zero: the GOAL ===
// No special members needed — std::string handles everything
class Person {
std::string name_;
int age_;
public:
Person(const std::string& name, int age)
: name_{name}, age_{age} {}
// Compiler generates correct copy/move/destructor automatically!
};
// === Rule of Five: when managing a raw resource ===
class CharBuffer {
char* data_;
std::size_t size_;
public:
explicit CharBuffer(const char* str)
: size_{std::strlen(str)}
, data_{new char[std::strlen(str) + 1]}
{
std::strcpy(data_, str);
}
// Destructor
~CharBuffer() { delete[] data_; }
// Copy constructor (deep copy)
CharBuffer(const CharBuffer& other)
: size_{other.size_}
, data_{new char[other.size_ + 1]}
{
std::strcpy(data_, other.data_);
}
// Copy assignment (copy-and-swap idiom)
CharBuffer& operator=(CharBuffer other) { // note: by value
swap(*this, other);
return *this;
}
// Move constructor
CharBuffer(CharBuffer&& other) noexcept
: data_{other.data_}, size_{other.size_}
{
other.data_ = nullptr;
other.size_ = 0;
}
// Move assignment
CharBuffer& operator=(CharBuffer&& other) noexcept {
if (this != &other) {
delete[] data_;
data_ = other.data_;
size_ = other.size_;
other.data_ = nullptr;
other.size_ = 0;
}
return *this;
}
friend void swap(CharBuffer& a, CharBuffer& b) noexcept {
using std::swap;
swap(a.data_, b.data_);
swap(a.size_, b.size_);
}
void print() const {
std::cout << (data_ ? data_ : "(null)") << '\n';
}
};
int main() {
CharBuffer a{"Hello"};
CharBuffer b = a; // copy constructor
CharBuffer c{"World"};
c = a; // copy assignment
CharBuffer d = std::move(a); // move constructor
d.print(); // Hello
a.print(); // (null) — moved-from state
return 0;
}=implicit și =șterge
Puteți solicita în mod explicit versiunea generată de compilator a unui membru special cu =default sau o puteți suprima în mod explicit cu =delete. Acest lucru vă face intenția clară și documentează semantica de copiere/mutare a clasei.
#include <memory>
#include <string>
// Non-copyable, movable resource handle
class FileHandle {
std::string path_;
int fd_;
public:
FileHandle(const std::string& path, int fd)
: path_{path}, fd_{fd} {}
// Explicitly default the destructor
~FileHandle() = default;
// Delete copy operations — file handles should not be duplicated
FileHandle(const FileHandle&) = delete;
FileHandle& operator=(const FileHandle&) = delete;
// Default move operations
FileHandle(FileHandle&&) = default;
FileHandle& operator=(FileHandle&&) = default;
};
int main() {
FileHandle fh{"data.txt", 3};
// FileHandle copy = fh; // ERROR: copy deleted
FileHandle moved = std::move(fh); // OK: move is allowed
return 0;
}Membrii sunt inițializați în ordinea în care sunt declarați în clasă, nu în ordinea în care apar în lista de inițializare. Dacă un membru depinde de altul, asigurați-vă că dependența este declarată mai întâi. Compilatorii avertizează despre acest lucru cu-Wreorder- nu ignorați niciodată acest avertisment. O eroare clasică: inițializareasize_dintr-un parametru, dar apoi utilizareasize_pentru a alocadata_, cudata_declarat înainte desize_.
- Utilizați
explicitpe constructorii cu un singur argument pentru a preveni conversiile implicite neintenționate - Lista de inițializare a membrilor este necesară pentru
constși membrii de referință și mai eficientă pentru toți membrii - Membrii se inițializează în ordinea declarației, nu în ordinea listei de inițializare
- Vizualizați Regula lui Zero - utilizați tipurile RAII, astfel încât membrii speciali generați de compilator să fie corecti
- Dacă scrieți oricare dintre destructor/copy-ctor/copy-assign, scrieți toate cele cinci (regula celor cinci)
=defaultdocuments intent;=deleteexplicitly forbids an operation
Test — Verifică-ți cunoștințele
(20 XP)1. What does the`explicit`keyword on a constructor prevent?
2. In what order are class members initialized?
3. Ce este Regula lui Zero?