目录
🚀博主:R6bandito_
🌏所属专栏:C++新特性
引言
在C++11中,新引入了两个关键字:final和override。这两个新特性的引入进一步加强了面向对象编程中的继承机制,提升代码的可读性和安全性,同时也能很好的防止在类编程中的一些潜在的问题。
final关键字
作用在类(class)上
如果你不希望某个类被继承,或者希望将这个类的设计封装在某个特定的逻辑范围内,可以使用final关键字来标记该类。一旦一个类被该关键字标记,则无法以任何形式被继承。示例如下:
class base final { //final关键字标记base类
private:
std::string str;
public:
base(const std::string& s) : str(s) {
std::cout << "base constructor" << std::endl;
}
virtual void display() {}
};
class Derived : public base { //报错!“不能将final类类型用作基类”
public:
using base::base;
};
作用在函数上
注意:这里首先得强调,final不能作用于普通全局函数或成员函数上面。
void Mutual() final {} //这种写法是绝对错误的
class Emtity {
public:
void test() final {
//同样是错误的写法
std::cout << "test" << std::endl;
}
};
final是作用于虚函数上面的,用于防止父类中的虚函数被子类进行重写。这对于一些核心算法或行为不允许修改的场景非常有用,能够防止派生类中的开发者无意中修改基类的重要功能。其正确的用法应如下:
class Base {
public:
virtual void test() final {
std::cout << "test" << std::endl;
}
};
class Derived : public Base {
void test() override {} //报错!final修饰的虚函数不允许被重写
};
这时有人可能会问
既然final用于防止子类重写父类虚函数,那为何不直接在父类中使用非虚函数呢?还要多此一举呢?
这是一个很有意思的问题,因为我们深知程序讲究的是效率,既然使用普通非虚成员函数可以达到相同效果,那还有必要去定义virtual函数并且使用final进行约束吗?问题的答案是肯定的。
答案其实很简单:因为virtual支持类间多态,而非虚函数很明显,只有通过函数重载来实现类内多态。
我们往往采用final进行约束,其目的在于需要实现多态,而不需要进一步被重写。觉得上述问题之所以有道理的朋友,很大可能是没有考虑到virtual的灵活性以及强大的多态性。
有时,我们仍然希望这个函数可以在某个派生类中被重写,以便实现多态的行为。然而,在某个派生类重写之后,可能由于设计上的原因或者逻辑复杂性,开发者不希望其他进一步的派生类再去改变它,因此才会使用到final。正如下例所示:
class Animal {
public:
virtual void speak() = 0;
};
class Dog : public Animal {
public:
void speak() final {
std::cout << "Woof!" << std::endl;
}
};
class Bulldog : public Dog {
public:
void speak() {
//报错!不允许继续重写了!
}
};
这是一个很经典的c++多态例子,基类Animal通过virtual函数在不同的派生类中实现了不同的功能。而在其某一级派生类中,我们发现其功能已经趋于圆满了,不需要再对其进行重写了,此时便可以使用final将其约束起来,防止其下的派生类无意之间对其进行了修改。
总而言之:虚函数是为了实现多态性,但在某些场景下,某个虚函数的重写可能是允许的,只是在特定的派生类之后需要限制。而非虚函数很明显无法满足这种需求,这便是这个问题的解答!
final关键字的优点
-
更完善的封闭设计:
当你希望某个类或某个函数的行为在继承链的某个阶段后完全确定,不允许后续类进行修改时,final
可以确保设计的封闭性。例如,库设计者常用final
来防止外部用户扩展库的关键类或重写某些核心算法。 -
额外的性能优化:
由于 final
阻止了继承或重写,编译器可以进行额外的优化。例如,编译器在知道某个函数不会被重写时,会为该函数内联优化或直接调用而不是通过虚函数表调用。
override关键字
override关键字只能作用于类中成员函数。
override
是 C++11 中引入的另一个非常重要的关键字,它的作用是明确告知编译器,某个函数是用来重写基类的虚函数。
如果一个函数带有 override
关键字,而基类中没有相应的虚函数,编译器会产生一个编译错误。
class Base {
public:
Base(){}
Base(int x){}
void display(){}
};
class Derived : public Base {
public:
using Base::Base;
void display() override{} //报错!重写的对应基类中没有该虚函数!
};
在 C++11 之前,C++ 允许你定义一个与基类同名的函数,而不强制检查其是否重写了基类的虚函数,这容易导致一些潜在错误。
如果不小心在派生类中定义了一个与基类虚函数同名但参数不匹配的函数,就可能导致函数没有成功重写,从而带来隐藏的逻辑问题。正如下面一例所示:
class Base {
public:
Base(){}
Base(int x){}
virtual void function(){}
};
class Derived : public Base {
public:
using Base::Base;
void function(int x) {}
};
上述例中,我们所期望的是重写基类中的function函数,但是由于无意间的参数错误,导致我们所期望的函数并没有被重写,而编译器却并没有给出任何错误警告,如果一直没有察觉这个潜藏的错误,后果可能不堪设想。
而如果我们使用override关键字进行修饰:
class Base {
public:
Base(){}
Base(int x){}
virtual void function(){}
};
class Derived : public Base {
public:
using Base::Base;
//得到报错:使用“override”声明的成员函数不能重写基类成员
void function(int x) override {}
};
这个错误警告能够帮助开发人员及时止损。
也就是说,当用户显式的使用override修饰某一函数时,就明确地告诉了编译器这是一个Derived类,同时这个函数将要重写Base类中的某一虚函数。
上述两个条件,即:1.是Derived类 2.重写Base类中虚函数 必须同时成立。当任一条件不成立时,编译器都不予通过。
override关键字的优点
- 提高了代码的可读性,让其他开发者能够一目了然地知道某个函数是对基类虚函数的重写。
- 当函数签名不匹配时,
override
会强制编译器进行检查,从而避免函数没有正确重写的潜在错误 ,使代码变得更加健壮。 - 在对类层次结构进行修改时,如果基类的虚函数签名发生改变,所有使用
override
的派生类函数都会在编译时被标记出错,从而减少了运行时的错误风险。
感谢支持!🌹🌹