object slicing (tsz. object slicings)
Képzeld el, hogy van egy torta (származtatott osztály), aminek az alapja egy piskóta (alaposztály). Amikor szeletelés történik, csak a piskótát tartjuk meg – a csoki-, gyümölcs-, vagy tejszínhabréteg elveszik.
class Base {
public:
int a;
void print() const {
std::cout << "Base\n";
}
};
class Derived : public Base {
public:
int b;
void print() const {
std::cout << "Derived\n";
}
};
void foo(Base b) {
b.print(); // Melyik print?
}
int main() {
Derived d;
foo(d); // Object slicing történik itt
}
Derived d
példányt érték szerint átadjuk a foo
függvénybe, ami Base
típusú paramétert fogad.Base
része másolódik át.Derived
saját részei (b
, Derived::print
) elvesznek – a Base::print()
fog lefutni.
Érték szerinti hozzárendelés:
Base b = Derived(); // slicing
Érték szerinti paraméterátadás:
void f(Base b); // slicing, ha Derived-et adunk át
Érték szerinti visszatérési érték:
Base g() {
Derived d;
return d; // slicing
}
Base
, nem Derived
class Shape {
public:
virtual void draw() const {
std::cout << "Drawing shape\n";
}
};
class Circle : public Shape {
public:
void draw() const override {
std::cout << "Drawing circle\n";
}
};
void render(Shape s) { // slicing
s.draw(); // "Drawing shape", nem "circle"
}
Ha
render(Circle());
hívást végzünk, az eredmény nem a várt “Drawing circle”, hanem csak aShape
-féle viselkedés.
void render(const Shape& s) {
s.draw(); // Megmarad a dinamikus viselkedés
}
void render(std::shared_ptr<Shape> s) {
s->draw();
}
Base(const Base&) = delete;
Base& operator=(const Base&) = delete;
Tartsd be az alapszabályt: öröklésnél mindig hivatkozással (vagy pointerrel) dolgozz.
C++ objektumok memóriaelrendezése:
Derived d +------------+------------+ | Base part | Derived b | +------------+------------+
Ha csak a Base
másolódik:
Base b = d; // Slicing után +------------+ | Base part | +------------+
A Derived b
tag, és a Derived::vtable
elveszik (ha nem referencia!).
Polimorfizmus működéséhez referenciára vagy pointerre van szükség. Ha másolás történik (slicing), a virtuális asztal (vtable
) információ elveszik – ez az oka, hogy Derived::draw()
helyett Base::draw()
fut le.
std::vector<Base> shapes;
shapes.push_back(Circle()); // slicing
Ezért használj:
std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(std::make_unique<Circle>());
Jellemző | Van Slicing | Nincs Slicing |
---|---|---|
Érték szerinti átadás | ✅ | ❌ |
Referencia | ❌ | ✅ |
Pointer | ❌ | ✅ |
Smart pointer | ❌ | ✅ |
Polimorfizmus működik | ❌ | ✅ |
Derived tagok elérhetők | ❌ | ✅ |
“Polimorfizmust csak pointeren vagy referencián keresztül lehet használni C++-ban.”
Ezért sose adj érték szerint származtatott példányt alap osztályú paraméternek.
Az object slicing nem fordítási hiba, de logikai hibákhoz vezethet, különösen ha örökléses hierarchiában dolgozol, és nem figyelsz oda, hogyan másolódnak az objektumok. A C++ nagy rugalmasságot ad, de ennek ára van – a fejlesztőnek kell biztosítania, hogy a típusinformáció megmaradjon.