move constructor (tsz. move constructors)
A C++ hagyományosan másoló konstruktort használ az objektumok másolására. Azonban néha a másolás nem optimális: - Nagy méretű objektumok esetén a másolás drága művelet. - Erőforrásokat kezelő objektumok esetén (pl. dinamikusan foglalt memória, fájlkezelők, hálózati kapcsolat) a másolat ugyanazokat az erőforrásokat kellene kezelje. - Felesleges másolások keletkezhetnek r-value értékek használatakor, például egy függvény visszatérési értékeként.
A move konstruktor ezt a problémát oldja meg azáltal, hogy az erőforrásokat egyszerűen átmásolja az új objektumba, miközben az eredeti objektumot “kiüríti”.
A move konstruktor speciális formája a konstruktornak, amely egy r-value referencia paramétert fogad (&&
jelöléssel):
class MyClass {
private:
int* data;
public:
// Konstruktor
MyClass(int value) {
data = new int(value);
std::cout << "Létrejött: " << *data << std::endl;
}
// Másoló konstruktor
MyClass(const MyClass& other) {
data = new int(*other.data);
std::cout << "Másolat készült: " << *data << std::endl;
}
// Move konstruktor
MyClass(MyClass&& other) noexcept {
data = other.data; // Átmásoljuk a pointert
other.data = nullptr; // Az eredeti objektumot "kiürítjük"
std::cout << "Mozgatva: " << *data << std::endl;
}
// Destruktor
~MyClass() {
if (data) {
std::cout << "Törölve: " << *data << std::endl;
delete data;
}
}
};
Hogyan működik? 1. Az MyClass&&
paraméter biztosítja, hogy az objektum egy r-value referenciát kapjon. 2. A move konstruktor átmásolja az erőforrásokat az új objektumba (data = other.data
). 3. Az eredeti objektum pointerét nullptr
-ra állítjuk (other.data = nullptr
), hogy az ne szabadítsa fel az erőforrást. 4. A destruktor csak akkor szabadítja fel az erőforrást, ha a data
nem nullptr
.
A move konstruktor az alábbi esetekben hívódik meg: - Ha egy r-value objektummal inicializálunk egy másik objektumot:
MyClass obj1(10);
MyClass obj2 = std::move(obj1); // Move konstruktor hívódik meg
Ha egy függvény visszatérési értéke egy lokális objektum:
MyClass createObject() {
return MyClass(20); // Move konstruktor hívódik meg
}
MyClass obj3 = createObject();
Ha egy konténer (pl. std::vector
) elemeit áthelyezzük:
std::vector<MyClass> vec;
vec.push_back(MyClass(30)); // Move konstruktor használata
A std::move
egy segédfüggvény a C++ standard könyvtárból, amely egy l-value objektumot r-value-vá alakít. Ez biztosítja, hogy a move konstruktor hívódjon meg másoló konstruktor helyett.
Példa:
MyClass obj1(50);
MyClass obj2 = std::move(obj1); // Move konstruktor hívódik meg
Fontos megjegyezni, hogy az obj1
már nem tartalmaz érvényes adatot a move után, ezért azt nem szabadna többé használni, kivéve ha azt újra inicializáljuk.
A move konstruktor különösen hatékony dinamikusan foglalt memória és nagy adatszerkezetek esetén, mert ahelyett, hogy az összes adatot lemásolná, csak a belső pointereket mozgatja.
Tegyük fel, hogy van egy osztály, amely egy nagy tömböt kezel:
class BigArray {
private:
int* arr;
size_t size;
public:
// Konstruktor
BigArray(size_t s) : size(s), arr(new int) {}
// Másoló konstruktor (lassú)
BigArray(const BigArray& other) : size(other.size), arr(new int) {
std::copy(other.arr, other.arr + size, arr);
}
// Move konstruktor (gyors)
BigArray(BigArray&& other) noexcept : size(other.size), arr(other.arr) {
other.arr = nullptr;
other.size = 0;
}
// Destruktor
~BigArray() { delete arr; }
};
Ha egy nagy méretű tömböt másolunk, akkor minden egyes elem átmásolódik, ami időigényes. A move konstruktor ehelyett csak a pointert mozgatja, így az áthelyezés gyors és hatékony.
Ha egy move konstruktor van egy osztályban, akkor érdemes implementálni a move assignment operátort is, amely a =
operátorhoz hasonlóan működik.
class MyClass {
private:
int* data;
public:
// Move konstruktor
MyClass(MyClass&& other) noexcept : data(other.data) {
other.data = nullptr;
}
// Move assignment operator
MyClass& operator=(MyClass&& other) noexcept {
if (this != &other) {
delete data; // Esetleg felszabadítjuk a régi erőforrást
data = other.data;
other.data = nullptr;
}
return *this;
}
~MyClass() { delete data; }
};
Helyzet | Másoló konstruktor | Move konstruktor |
---|---|---|
Objektumot l-value-ból hozunk létre | ✅ Használjuk | ❌ Nem hívódik meg |
Objektumot r-value-ból hozunk létre | ❌ Feleslegesen másol | ✅ Hatékonyabb |
Objektum visszatérési érték egy függvényben | ❌ Lehetséges másolás | ✅ Mozgatás történik |
Objektumot std::vector vagy más konténer használja
|
❌ Lassú másolás | ✅ Gyors áthelyezés |
&&
) és std::move
biztosítják, hogy a move konstruktor működjön.A move konstruktor modern C++ egyik leghasznosabb optimalizálási lehetősége, és különösen fontos teljesítménykritikus alkalmazásokban.