系列文章
C++11之正则表达式(regex_match、regex_search、regex_replace)
C++11之线程库(Thread、Mutex、atomic、lock_guard、同步)
C++11之智能指针(unique_ptr、shared_ptr、weak_ptr、auto_ptr)浅谈内存管理
C++11之强制类型转换(static_cast,const_cast,dynamic_cast,reinterpret_cast)
C++11之Lanbda表达式(匿名函数)
C++11之右值引用:移动语义和完美转发(带你了解移动构造函数、纯右值、将亡值、右值引用、std::move、forward等新概念)
文章目录
- 系列文章
- 场景
- 继承类的创建释放顺序
- 通过using关键字继承
- 继承成员函数
- 继承构造函数
- 带有默认参数的构造函数
- 继承构造函数导致的“冲突”
- 继承构造函数的优缺点
- 优点
- 缺点
场景
首先我们来看下面这段代码,有一个Class A、还有一个继承于A的Class B,Class B仅仅增加了一个虚函数,但是就需要把Class A的所有构造方法进行“补齐”。
class A
{
public:A(int i){}A(double d, int i){}A(float f, int i, const char* c){}
};class B : public A
{
public:B(int i):A(i) {}B(double d, int i):A(d, i) {}B(float f, int i, const char* c):A(f, i, c) {}virtual void test(){}
};
继承类的创建释放顺序
就以上面A为父类,B为子类为例,当创建一个B类的对象时,先调用父类的构造函数,然后调用自身的构造函数,在进行释放时,先调用自身的析构函数,然后在调用父类的析构函数。(注: 如果有读者不太了解可以在子类和父类中的构造、析构函数中进行日志输出观察。这块不是重点就一笔带过。)
通过using关键字继承
那么对于上述这个例子中,如果子类B在构造函数中不调用父类A对应的构造函数的话,编译器就会自动调用父类A的默认构造函数,结果A没有默认构造函数就会报错。
所以为了解决父类中有好多的构造函数,而导致出现这种繁琐的步骤,在C++11中就引入了,子类可以通过using关键字,通过声明继承父类构造函数。
继承成员函数
这里定义了一个父类Base
,并有一个参数为double
的f函数。又定义了一个子类Derived
继承于Base
,其中Derived
类中有一个参数为int
的f
函数,并且在Derived中使用了using Base::f;
表示在Derived
中也可以使用父类的f
函数。
class Base
{
public:void f(double i){cout << "Base:" << i << endl;}
};class Derived : public Base // 默认为public继承
{
public:using Base::f;void f(int i){cout << "Derived:" << i << endl;}
};int main()
{B(1);Base b;b.f(4.6);Derived d;d.f(4.9);d.f(4);return 0;
}
运行结果:
Base:4.6
Base:4.9
Derived:4
这里我们通过using声明,就可以看到子类Derived也可以使用父类的f
函数。
继承构造函数
这里对文中最开始的那个代码进行一下改造。
class A
{
public:A(int i){}A(double d, int i){}A(float f, int i, const char* c){}
};class B : public A
{
public:using A::A; // 继承构造函数virtual void test() {};
};
在子类B中只需要添加一句using A::A;
,就可以继承构造函数。
带有默认参数的构造函数
在父类带有默认参数的构造函数,对于继承构造函数来说,参数的默认值是不会被继承的。但是,默认值会导致父类产生较多个构造函数的版本,并且所有的构造函数均会被子类继承。
class A
{
public:A(int a = 3, double = 2.4){}
};class B : public A
{
public:using A::A;
};
以上面这段代码为例,就A(int a = 3, double = 2.4)
一个构造函数能产生多少个构造函数版本呢??
A类中会有这些:
- A(int =3, double =2.4) 这就是俩个参数的版本
- A(int =3)这是只有一个参数的版本
- A(const A&)这是拷贝构造函数版本
- A() 这是无参构造函数版本
B类中会有这些:
- B(int, double) 这就是俩个参数的版本
- B(int =3)这是只有一个参数的版本
- B(const A&)这是拷贝构造函数版本 (这不是继承的)
- B()无参构造函数
所以在父类中有默认构造函数时,需要特别小心多个版本的构造函数的产生。
继承构造函数导致的“冲突”
这种冲突一般在多继承情况下才会存在 ,由于父类中有相同的构造函数导致的子类中有重复定义类型相同的继承构造函数。
class A
{
public:A(int a){}
};class B
{
public:B(int b) {}
};class C : public A, public B
{
public:using A::A;using B::B;
};
例如上面这个例子,这时我们就可以通过显式定义冲突的构造函数,这样就可以阻止隐式生成的继承构造函数导致的冲突。
class C : public A, public B
{
public:using A::A;using B::B;C(int c); // 显式声明冲突的构造函数
};
继承构造函数的优缺点
优点
通过这种继承构造函数的方式是隐式声明的。也就表示如果这个继承构造函数不使用,那么编译器就不会为其产生真正的函数。通过这个方式生成子类的各种构造函数比原有的方法更加简洁并且高效。
缺点
继承构造函数只能初始化父类中的成员,对于子类中成员变量只能通过C++11的初始化表达式进行设定默认值,具有一定的局限性。
如果父类的构造函数是在private作用域下,或者是子类虚继承父类,这种情况下就不能在子类中生成继承父类的构造函数了。而且子类一旦使用了继承构造函数,那么编译器就不会为子类生成默认的构造函数了。