C++ 11: Qualifiers for functions: override - final and delete - default

Discussion in 'C++' started by BiplabKamal, Apr 16, 2016.

  1. Objective of a programming language is to make the life of programmers easy. Human programmers do mistakes while writing code. While writing code you might be writing something different from what is in your mind. This can be due to typos, absent mindedness, misunderstanding the language syntax or features, wrong usage of keywords or identifiers and so on. The power of the compiler is not only in giving lot of tools to the programmers but also restricting them from doing mistakes. C++ as a powerful language in giving a lot of flexibility to the programmers had to compromise in the area of error checking. Good news is that C++11 has brought a lot of improvement to provide features to keep programmers away from doing mistakes. override, final, delete and default identifiers are such features which helps you to handle functions correctly.

    override



    Look at the following classes where base class has virtual function f1(float f) and derived class overrides it
    Code:
    #include <iostream>
    #include <iostream>
    #include<memory>
    using namespace std;
    class MyBase
    {
    public:
        virtual void f1(float f)
        {
            cout << "MyBase::f1(float f)" << endl;
        }
    };
    class MyDerived :public MyBase
    {
    public:
        virtual void f1(int f)
        {
            cout << "MyDerived::f1(int f)" << endl;
        }
    };
    int main()
    
    {
        unique_ptr< MyBase> pd(new MyDerived);
        pd->f1(2.4);//Will call base class function not the derived class function
        return 0;
    }
    
    Which function is called in the main function? You might have already got the problem. If not, revisit the code again. In the derived class the function f1(int f) has different signature though the intention was to override the base method. Compiler in this case cannot detect what is in your mind but create another virtual method(overloaded) in the derived class. You can specify your intention to the compiler using ‘override’ identifier. In this case compiler will generate error.

    Overriding a base class method in derived class has to follow the override rules:
    1. base class method must be virtual
    2. The base and derived class methods name must be same except the destructor
    3. The parameter types of both base and derived methods must be identical
    4. The constness of both base and derived methods must be identical
    5. The return type and exception specification for both base and derived methods must be compatible
    6. Reference qualifiers for both base and derived methods must be identical
    Above long list of requirements indicates that a small mistake can make a big difference. More over overriding error does not invalidate the code, so compiler will not complain but it will not work as intended. For example following code is legal and looks like no override error but not a single method override took place here.
    Code:
    class MyBase
    {
    public:
        virtual void f1() const;
        virtual void f2(int i);
        virtual void f3() &;
        void f4() const;
    };
    class MyDerived :public MyBase
    {
    public:
        virtual void f1() ;
        virtual void f2(unsigned i);
        virtual void f3() && ;
        void f4() const ;
    };
    
    If you specify your intention by using override identifier the compiler will generate error. The derived class methods with override identifier will look like:
    Code:
    class MyBase
    {
    public:
        virtual void f1() const;
        virtual void f2(int i);
        virtual void f3() &;
        void f4() const;
    };
    class MyDerived :public MyBase
    {
    public:
        virtual void f1() override; //Error: const is missing
        virtual void f2(unsigned i) override; // Error: unsigned in place of int
        virtual void f3() && override; // Error: rvalue qualified instead of lvalue qualified
        void f4() const override;// Base class method is not virtual
    };
    
    To get the code complied with override methods is shown below
    Code:
    class MyBase
    {
    public:
        virtual void f1() const;
        virtual void f2(int i);
        virtual void f3() &;
        virtual void f4() const;
    };
    class MyDerived :public MyBase
    {
    public:
        virtual void f1() const override;
        virtual void f2(int i) override;
        virtual void f3() & override;
        void f4() const override;
    };
    

    final



    Similarly if you want to restrict derived classes from overriding then ‘final’ identifier is used. Here is the example of using both the identifiers:
    Code:
    #include <iostream>
    using namespace std;
    class MyBase
    {
    public:
        virtual void f1(float f) 
        {
            cout << "MyBase::f1(float f)" << endl;
        }
        virtual void f2(float f)
        {
            cout << "MyBase::f2(float f)" << endl;
        }
    
    };
    class MyDerived :public MyBase
    {
    public:
        void f1(int f) override // Will generate error. Does not find the base class member
        {
            cout << "MyDerived::f1(int f)" << endl;
        }
        void f2(float f) override final // Overrides and at the same time makes it final
        {
            cout << "MyDerived::f2(float f)" << endl;
        }
    };
    class MyDerivedDerived :public MyDerived
    {
    public:
        void f1(float f) override // Overrides the Mybase::f1(float f) 
        {
            cout << "MyDerivedDerived::f1(float f)" << endl;
        }
        void f2(float f) override // Will generate error. Cannot override a final function
        {
            cout << "MyDerivedDerived::f2(float f)" << endl;
        }
    };
    
    ‘final’ identifier can also be used for class to make the class unavailable for inheritance. A final class cannot be used a base class. Following code shows the use of final class.
    Code:
    class MyBase
    {
    };
    class MyDerived final :public MyBase 
    {
    };
    class MyDeriveDerived :public MyDerived // Error:Cannot inherit
    {
    };
    

    Defaulted and Deleted function



    Frequently we come across situation where we want to restrict the client code of a class from creating a copy of an existing object of that class. For the said purpose we, the c++ programmers used to make the copy constructor and copy assignment operator inaccessible to the client code. How do we do that? Even we don’t provide our own copy constructor and copy assignment operator the compiler will provide one which are called default copy constructor and default copy assignment operator. So how do we force the compiler not to provide default implementation of those functions. OK, rule is that if you provide your own version of those methods compiler will not provide any one. So what can we do? Simple, just make the compiler fool. Just declare the copy constructor and copy assignment operator in the private section of your class deceleration. You are doing two things here, stopping the compiler from generating it’s own version and making those functions inaccessible to the client by making them private. No need to give implementation because implementation is required only at the link time. In this case compilation will fail, so no question of linking. For example:
    Code:
    class MyClass
    {
    private:
        int data;
        MyClass(const MyClass&);// Copy constructor as private
    public:
        MyClass(int i):data{i}
        {
    
        }
    };
    
    int main()
    {
        MyClass obj1(100);
        MyClass obj2 = obj1; //Error: 'MyClass::MyClass': cannot access private member declared in class 'MyClass'
        return 0;
    }
    
    But there is a catch here , suppose there is some code like member functions or friends of the class still access them they will get the error only at link time, not at compile time if the implementation is not there. More over if you forget to delete previously created implementation, then the client will go without any notification. Following code will use private copy constructor and still will be compiled and but get link error:
    Code:
    class MyClass
    {
    private:
        int data;
        MyClass(const MyClass& obj);// Copy constructor as private
        
    public:
        MyClass(int i):data{i}
        {
    
        }
        friend MyClass& GetData(MyClass& obj); //Friend function
    };
    MyClass& GetData(MyClass& obj)
    {
        MyClass retobj = obj; // Friend function still has access to the copy constructor
        return retobj;
    }
    int main()
    {
        MyClass obj1(100);
        MyClass& obj2= GetData(obj1);
        return 0;
    }
    
    C++11 brings a better way of achieving same objective using “=delete” to make special functions like the copy constructor and copy assignment operator as deleted function. The major advantage of this method is that a deleted method is no more accessible and will give better compilation error. Compare the following code with the above code:
    Code:
    class MyClass
    {
    private:
        int data;
    public:
        MyClass(int i):data{i}
        {
    
        }
        MyClass(const MyClass& obj) = delete; // Deleted copy constructor
        friend MyClass& GetData(MyClass& obj);
    };
    MyClass& GetData(MyClass& obj)
    {
        MyClass retobj = obj; // Compile errror: 'MyClass::MyClass(const MyClass &)': attempting to reference a deleted function
        return retobj;
    }
    int main()
    {
        MyClass obj1(100);
        MyClass& obj2= GetData(obj1);
        return 0;
    }
    
    Another advantage of “=delete” is that it not only applies to member functions but applies to all functions. This is very helpful when you want to delete some overloaded versions of a function. Take the following example:
    Code:
    int factorial(int n)
    {
        if (n <= 1)
            return n;
        else
            return n*factorial(n-1);
    }
    
    int main()
    {
        long f{ 0 };
        f = factorial(2);
        f = factorial(2.4);
        f = factorial('a');
        f = factorial(true);
        return 0;
    }
    
    The above code will compile because there is an implicit conversion from float, char and bool. But it does not make sense. So if you want to avoid such calls you should delete those overloaded version. This means you will write following functions as deleted so that those call cannot be made.
    Code:
    int factorial(double) = delete;
    int factorial(char) = delete;
    int factorial(bool) = delete;
    
    This is also useful when you have a template function and you want to disallow instantiation of the template function for some type arguments. Suppose you have the following template function:
    Code:
    template<class T>
    unsigned GetObjectSize(T* pObj)
    {
        unsigned  nResult{ 0 };
        if (pObj != nullptr)
        {
            nResult = sizeof(*pObj);
        }
        return nResult;
    }
    
    In this function if you pass void* it will not compile. If you pass a character array it will not give size of the array. Special cases like these need special handling. Suppose you decided to reject those calls as special handling. How do you do that? With C++11 you can delete those template specialization shown below so the compiler rejects those call.
    Code:
    unsigned GetObjectSize(char*) = delete;
    
    Even the template function is inside a class you cannot make some instance as private but can delete them outside the class.

    There is also a feature opposite to deleting function. For a class you can delete a special function generated by the compiler, similarly you can say to the compiler that you want a default implementation when compiler does not generate one by default. For example if you provide your own implementation of the constructor different than the default one the compiler will not generate any default constructor. Even you implement the copy constructor compiler will stop generating any default constructor. But still if you want to use them you can specify that by using “=default” at the end of function deceleration. In the following code as the copy constructor defined the default constructor becomes unavailable. If you want to avail default construction of MyClass object either you need to define the default constructor or specify that you want to still use the default version provided by the compiler.
    Code:
    class MyClass
    {
    private:
        int data;
    public:
        MyClass(const MyClass& obj) :data{obj.data} // Copy constructor
        {
            
        }
    };
    
    int main()
    {
        MyClass obj1;//Error: No default constructor
        return 0;
    }
    
    To remove the compilation error of the above code you can modify the code to use default constructor as below:
    Code:
    class MyClass
    {
    private:
        int data;
    public:
        MyClass() = default;
        MyClass(const MyClass& obj) :data{obj.data} // Copy constructor
        {
            
        }
    };
    
    int main()
    {
        MyClass obj1;
        return 0;
    }
    This way you can use the default special functions like constructor, copy constructor, move constructor, copy assignment operator and move assignment operator.

    C++11 has given the new features. If you use it error will be detected earlier. But if you don’t use it some may go undetected during compilation, linking or even testing to impose higher cost being detected after delivery.
     

Share This Page

  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice