Tags

  • AWS (7)
  • Apigee (3)
  • ArchLinux (5)
  • Array (6)
  • Backtracking (6)
  • BinarySearch (6)
  • C++ (19)
  • CI&CD (3)
  • Calculus (2)
  • DesignPattern (43)
  • DisasterRecovery (1)
  • Docker (8)
  • DynamicProgramming (20)
  • FileSystem (11)
  • Frontend (2)
  • FunctionalProgramming (1)
  • GCP (1)
  • Gentoo (6)
  • Git (15)
  • Golang (1)
  • Graph (10)
  • GraphQL (1)
  • Hardware (1)
  • Hash (1)
  • Kafka (1)
  • LinkedList (13)
  • Linux (27)
  • Lodash (2)
  • MacOS (3)
  • Makefile (1)
  • Map (5)
  • MathHistory (1)
  • MySQL (21)
  • Neovim (10)
  • Network (66)
  • Nginx (6)
  • Node.js (33)
  • OpenGL (6)
  • PriorityQueue (1)
  • ProgrammingLanguage (9)
  • Python (10)
  • RealAnalysis (20)
  • Recursion (3)
  • Redis (1)
  • RegularExpression (1)
  • Ruby (19)
  • SQLite (1)
  • Sentry (3)
  • Set (4)
  • Shell (3)
  • SoftwareEngineering (12)
  • Sorting (2)
  • Stack (4)
  • String (2)
  • SystemDesign (13)
  • Terraform (2)
  • Tree (24)
  • Trie (2)
  • TwoPointers (16)
  • TypeScript (3)
  • Ubuntu (4)
  • Home

    Understanding C++ Casts

    Published Jul 23, 2019 [  C++  ]

    C++, being a strongly typed language, is strict with its types. And there are always cases when you need to convert one type into another, which is known as casting.

    int a = 10;
    long b = a;
    

    This is known as implicit conversion. More specifically, the above example is of standard conversion, which occurs automatically between fundamental types and few pointers.

    There can also be implicit casts between classes with constructor or operator conversions.

    struct A {};
    struct B {
        B (A a) {}
    }
    
    A a;
    B b = a;
    

    Here an implicit conversion happened from A to B because B has a constructor that takes an object of A as parameter.

    static_cast

    static_cast can be used to convert between pointers to related classes (up or down the inheritance hierarchy). It can also perform implicit conversions.

    class Mammal {};
    class Human : public Mammal {};
    
    Human* h = new Human;
    
    Mammal* m = static_cast<Mammal *>(h);   // cast it to pointer to base type. static_cast here is unnecessary
    
    Mammal* m2 = static_cast<Human *>(m);   // cast back to pointer to derived type
    

    When we’re casting up the hierarchy, static_cast is not needed. Every Human is a Mammal. So a Human * can be converted to a Mammal * implicitly. static_cast would actually perform this implicit cast if you use it anyway.

    But every mammal may not be a Human. So implicit conversion from Mammal * to Human * is not allowed. That’s where static_cast comes in. If we omit the static cast, we’ll get an error something alone the line of

    invalid conversion from 'Mammal*' to 'Human *'

    But, using static_cast, we can still do the conversion anyway. static_cast doesn’t perform any checks. So it’s the programmer duty to ensure that the conversion should be valid. An invalid conversion might not fail, but can cause problems later when the pointer points to an incomplete type and is dereferenced.

    class Mammal {};
    class Human : public Mammal {
        public:
            virtual void scream () { // note the virtual
                cout << "MOM" << endl;
            }
    }
    
    Mammal* m = new Mammal;
    Human* h = static_cast<Human *>(m); // OK so far
    h->scream();    // Mayhem!!
    

    So don’t use static_cast to cast down the hierarchy unless you’re absolutely sure the conversion is valid.

    static_cast won’t let you convert between two unrelated classes

    class A {};
    class B {};
    A* a = new A;
    B* b = static_cast<A *>(a);
    

    Gives an error -

    cannot convert 'A*' to 'B*' in initialization

    dynamic_cast

    dynamic_cast is related to static_cast in the sense that it helps to cast through inheritance, but it’s more powerful than static_cast but has an overhead. It performs a check in order to prevent the case above.

    class Mammal {};
    class Human : public Mammal {};
    
    Human* h = new Human;
    Mammal* m = dynamic_cast<Mammal*>(h);   // ok
    Human* h1 = dynamic_cast<Human*>(m);    // error
    

    The second conversion would produce a compilation error since base-to-derived conversion are not allowed with dynamic_cast unless the base class is polymorphic (has at least one virtual function, either declared or through inheritance).

    class Mammal {
        public:
            virtual void scream(){}
    };
    
    class Human : public Mammal {
        public:
            void scream() override {
                cout << "MOM" << endl;
            }
    }
    
    Human* h = new Human;
    Mammal* m = dynamic_cast<Mammal *>(h);
    Human* h1 = dynamic_cast<Human *>(m);
    h1->scream();
    

    This works as you’d expect.

    What happens if we try to cast a Mammal * to a Human * where the Mammal is not actually a Human? (that crashed the static_cast one)

    Mammal* m = new Mammal;
    Human* h = dynamic_cast<Human *>(m);
    
    if(h == nullptr) {
        cout << "Oops! Cast failed!" << endl;   // this will be the result
    } else {
        h->scream();
    }
    

    So, as you can see, dynamic_cast performs a check. It returns nullptr if you’re trying to convert pointers or throws std::bad_cast if you’re trying to convert references.

    But remember, this check happens in runtime, and not compile time. It require the Run-Time Type Information (RTTI) to keep track of dynamic types and thus has a slight overhead.

    Let’s see now what happens when we try to convert two unrelated classes

    class A {
        public:
            void f(){}
    };
    
    class B {};
    
    A* a = new A;
    B* b = dynamic_cast<A *>(a);
    

    This doesn’t give a compilation error (unlike static_cast, because the check is performed at run time at that point b will be nullptr)

    Finally, one more thing dynamic_cast can do is “side cast.” To understand this, consider this classic “dreaded diamond” hierarchy

    struct A {
        virtual void f() = 0;
    }
    
    struct L : A {
        virtual void f() override {
            cout << "Left" << endl;
        }
    }
    
    struct R : A {
        virtual void f() override {
            cout << "Right" << endl;
        }
    }
    
    struct D : L, R {
    
    }
    

    By side casting, we mean to say that we should be able to cast an object of type L as type R and it should behave exactly as type R (and vice versa). This is of course possible only when the underlying object is actually of type D. static_cast however, can’t help here.

    D* d = new D;
    L* l = d;
    R* r= d;
    A* bl = l;
    A* br = r;
    br->f();    // Right
    
    static_cast<L *>(br) -> f(); // still prints "Right"
    dynamic_cast<L *>(br) -> f(); // prints "Left"bl -> f(); // prints "Left";
    static_cast<R *>(bl) -> f(); // still prints "Left"
    dynamic_cast<R *>(bl) -> f(); // prints "Right"
    

    const_cast

    const_cast is the only cast that can be used to add const to a type or take const out of a type. This is useful when, say you want to pass a non const argument to a function which expects const arguments.

    void f(int& x) {
        x = 5;
    }
    

    It expects a non const reference to int.

    Now suppose you have something like this

    int i = 4;  // non const variable
    const int& j = i;   // const reference
    

    If you try to call f with j as argument, you’ll get an error

    error: binding reference of type 'int&' to 'const int' discards qualifiers

    What you can do, is to remove the const with const_cast

    f(const_cast<int&>(j));
    

    Note however, that you can remove const away from an object only if it was actually declared as non const. Removing const from a const object is undefined behavior

    const int i = 3;
    int* p = const_cast<int*>(&i);
    *p = 4; // undefined behavior
    

    Similarly, you can also add const to an object. const_cast works on volatile keyword too, although that’s rare.

    reinterpret_cast

    This cast converts any type of pointer to any other type of pointer, even unrelated types. No checks are performed. It simply copies the binary data from one pointer to another.

    All types of pointer conversions are allowed. You can even cast pointers to and from integer types. The only guarantee is that if you cast a pointer to an integer type that is large enough to hold it back, and then cast it back to pointers, you get a valid pointer

    References: https://codeburst.io/understanding-c-casts-ef1f36e54240