The final specifier serves two primary purposes in C++: preventing class inheritance and prohibiting function overriding. When applied to a class, it prevents that class from being used as a base class. When applied to a virtual function, it ensures that function cannot be overridden in derived classes. 2. The override Specifier
The override specifier provides a clear indication when a derived class function is intended to override a base class virtual function. This improves code readability and allows the compiler to verify that an override actually exists in the base class. If a function marked with override doesn't properly override a base class function, the compiler will generate an error. This helps catch mistakes like typographical errors in function signatures that would otherwise create unexpected behavior. 3. Pure Virtual Functions
Pure virtual functions enable the creation of abstract classes - classes that represent general concepts with specific implementations deferred to derived classes. For example, Shape might be an abstract class with pure virtual functions like area() and perimeter(), while Circle and Rectangle provide concrete implementations. A pure virtual function is declared by appending = 0 to the function declaration: ```
class Shape {
public:
virtual double area() const = 0;
virtual double perimeter() const = 0;
};
A class containing at least one pure virtual function becomes an abstract class and cannot be instantiated. The only operations allowed on such classes are static member access and pointer/reference operations. 4. Complex Inheritance and Polymorphism Issues
----------------------------------------------
### 4.1 Polymorphic Calls to Overridden Functions
Consider the following code example: ```
#include <iostream>
class Base {
public:
virtual void display(int value = 0) {
// Default parameter
}
};
class Derived final : public Base {
public:
void display(int value) {
std::cout << "Value: " << value << std::endl;
}
};
int main() {
Base* ptr = new Derived();
ptr->display(); // What happens here?
return 0;
}</iostream>
When polymorphism and overriding are involved, fucntion calls follow the "base class declaration + derived class definition" pattern. The default parameter from the base class is used, but the function body from the derived class is executed. This explains why only the base class needs to specify virtual - the derived class function signature is irrelevant in polymorphic context (though still important for non-polymorphic calls). ### 4.2 'this' Pointer Type Changes in Inheritance
Consider this example: ``` #include
class Base { public: virtual void process(int value = 0) {}
void helper() {
process();
}
};
class Derived final : public Base { public: void process(int value = 1) { std::cout << "Processing with value: " << value << std::endl; } };
int main() { Derived* d = new Derived(); d->helper();
return 0;
}
When a derived class calls a base class member function, the implicit `this` pointer undergoes an "assignment-compatible conversion" from Derived* to Base*. This conversion affects only how members are accessed, not the pointer value or the object it points to. This behavior is consistent with polymorphism requirements, as demonstrated in the output. ### 4.3 Special Handling of Access Control in Polymorphism
Consider this example: ```
#include <iostream>
class Base {
public:
virtual void show() {
std::cout << "Base class" << std::endl;
}
};
class Derived : public Base {
private:
void show() override {
std::cout << "Derived class" << std::endl;
}
};
int main() {
Base* p = new Derived();
p->show(); // Is this allowed?
return 0;
}</iostream>
Direct access to Derived::show() through a Base* pointer is allowed due to special polymorphism rules. Access specifiers in derived classes don't restrict access when calling through base class pointers/references. This works because polymorphic calls use the virtual function table directly, bypassing normal access control mechenisms. The access level is determined by the base class's access specifiers. 5. Dynamic vs. Static Binding
Binding refers to the process of determining which function to call. Both dynamic and static binding serve this purpose but operate differently. ### 5.1 Dynamic Binding
Dynamic binding (runtime binding) is fundamental to polymorphism. It uses the virtual function table to determine the function address at runtime. ### 5.2 Static Binding
Static binding (compile-time binding) applies to non-virtual functions, function overloading, and template functions. The function address is determined at compile/link time. When using qualified calls (e.g., p->Base::show()), dynamic binding is overridden, and static binding is used, losing polymorphic behavior. 6. Best Practices and Technical Details
- Use
constfor member functions to enable calls with anonymous objects 2. Member function addresses require the&operator when printing 3. Useprintffor reliable address printing, ascouthas issues with function pointers 4. Implicit type conversions occur between related types (e.g., integer types, pointers) 5. Only member functions can be virtual; static and global functions cannot 6. When separating virtual function declarations and definitions, omitvirtualin the definition 7. Friend functions cannot be virtual as they're not class members 8. Multiple inheritance may result in multiple virtual function tables, ordered by inheritance sequence 9. Non-overridden virtual functions share the same address across classes but occupy different virtual table spaces 10. Virtual function tables are constructed during compilation, while polymorphic binding occurs at runtime