SyntaxStudy
Sign Up
C++ Slicing, Upcasting, and Downcasting
C++ Beginner 1 min read

Slicing, Upcasting, and Downcasting

Object slicing occurs when a derived object is assigned or passed by value to a base class variable. Only the base portion is copied; the derived-specific data and virtual function overrides are lost. This subtle bug is avoided by always using pointers or references when working with polymorphic objects. An upcast — converting a derived pointer or reference to a base type — is always safe and implicit in C++. A downcast — converting a base pointer or reference back to a derived type — may be unsafe if the dynamic type does not match. The 'dynamic_cast' operator performs a runtime type check and returns 'nullptr' for pointers (or throws 'std::bad_cast' for references) when the cast is invalid, making it the safe choice for downcasting in polymorphic hierarchies. 'static_cast' performs downcast without a runtime check and is only appropriate when the programmer guarantees the dynamic type is correct — for instance, inside a known switch-dispatch. 'reinterpret_cast' and C-style casts bypass the type system entirely and should be avoided except for low-level interoperability code.
Example
#include <iostream>
#include <memory>
#include <vector>

struct Shape {
    virtual ~Shape() = default;
    virtual std::string type() const { return "Shape"; }
    virtual double area()     const { return 0.0; }
};

struct Circle : Shape {
    explicit Circle(double r) : radius(r) {}
    std::string type() const override { return "Circle"; }
    double area()      const override { return 3.14159 * radius * radius; }
    double radius;
};

struct Square : Shape {
    explicit Square(double s) : side(s) {}
    std::string type() const override { return "Square"; }
    double area()      const override { return side * side; }
    double side;
};

void printSliced(Shape s) {   // BAD: slices derived info
    std::cout << "sliced type: " << s.type() << "\n";
}

int main() {
    Circle c(5.0);
    printSliced(c);   // prints "Shape", not "Circle"

    // Correct: use pointer/reference
    std::vector<std::unique_ptr<Shape>> shapes;
    shapes.push_back(std::make_unique<Circle>(3.0));
    shapes.push_back(std::make_unique<Square>(4.0));

    for (const auto& s : shapes) {
        std::cout << s->type() << " area = " << s->area() << "\n";

        // Safe downcast with dynamic_cast
        if (auto* circle = dynamic_cast<Circle*>(s.get())) {
            std::cout << "  radius = " << circle->radius << "\n";
        }
    }

    return 0;
}