MST

星途 面试题库

面试题:C++缺省函数在继承体系中的行为及优化

在一个复杂的C++继承体系中,基类和派生类都可能涉及编译器自动生成的四个缺省函数。请深入分析在继承关系下,这些缺省函数的调用顺序、行为表现,以及如何针对继承体系下的特点进行优化,以确保代码的高效性和正确性,同时举例说明在多重继承和虚继承场景下的特殊情况。
10.5万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

1. 四个缺省函数

C++中编译器自动生成的四个缺省函数为:默认构造函数、拷贝构造函数、拷贝赋值运算符和析构函数。

2. 调用顺序和行为表现

  • 默认构造函数
    • 调用顺序:在创建派生类对象时,先调用基类的默认构造函数,再调用派生类的默认构造函数。如果基类没有默认构造函数,派生类必须在其构造函数初始化列表中显式调用基类的其他构造函数。
    • 示例
class Base {
public:
    Base() { std::cout << "Base default constructor" << std::endl; }
};
class Derived : public Base {
public:
    Derived() { std::cout << "Derived default constructor" << std::endl; }
};
int main() {
    Derived d;
    return 0;
}

输出:

Base default constructor
Derived default constructor
  • 拷贝构造函数
    • 调用顺序:先调用基类的拷贝构造函数,再调用派生类的拷贝构造函数。它会复制基类部分和派生类新增的成员变量。
    • 示例
class Base {
public:
    Base(const Base& other) { std::cout << "Base copy constructor" << std::endl; }
};
class Derived : public Base {
public:
    Derived(const Derived& other) : Base(other) { std::cout << "Derived copy constructor" << std::endl; }
};
int main() {
    Derived d1;
    Derived d2(d1);
    return 0;
}

输出:

Base default constructor
Base copy constructor
Derived copy constructor
  • 拷贝赋值运算符
    • 调用顺序:先调用基类的拷贝赋值运算符,再执行派生类自己的赋值操作。如果派生类有自定义的动态资源管理,需要正确处理基类部分和自身部分的赋值。
    • 示例
class Base {
public:
    Base& operator=(const Base& other) {
        std::cout << "Base copy assignment operator" << std::endl;
        return *this;
    }
};
class Derived : public Base {
public:
    Derived& operator=(const Derived& other) {
        Base::operator=(other);
        std::cout << "Derived copy assignment operator" << std::endl;
        return *this;
    }
};
int main() {
    Derived d1, d2;
    d1 = d2;
    return 0;
}

输出:

Base default constructor
Base default constructor
Base copy assignment operator
Derived copy assignment operator
  • 析构函数
    • 调用顺序:与构造函数相反,先调用派生类的析构函数,再调用基类的析构函数。这样能确保派生类新增的资源先被释放,再释放基类的资源。
    • 示例
class Base {
public:
    ~Base() { std::cout << "Base destructor" << std::endl; }
};
class Derived : public Base {
public:
    ~Derived() { std::cout << "Derived destructor" << std::endl; }
};
int main() {
    Derived d;
    return 0;
}

输出:

Base default constructor
Derived default constructor
Derived destructor
Base destructor

3. 优化措施

  • 显式定义:如果类有动态分配的资源(如指针成员),应显式定义拷贝构造函数、拷贝赋值运算符和析构函数(遵循RAII原则),以避免浅拷贝等问题。
  • 使用= default= delete:如果编译器生成的缺省函数满足需求,使用= default让编译器生成;如果不需要某些缺省函数(如禁止拷贝),使用= delete。例如:
class NonCopyable {
public:
    NonCopyable() = default;
    NonCopyable(const NonCopyable&) = delete;
    NonCopyable& operator=(const NonCopyable&) = delete;
    ~NonCopyable() = default;
};

4. 多重继承和虚继承场景下的特殊情况

  • 多重继承
    • 调用顺序:构造函数按照继承列表的顺序调用各个基类的构造函数,析构函数则按相反顺序调用。
    • 示例
class Base1 {
public:
    Base1() { std::cout << "Base1 constructor" << std::endl; }
    ~Base1() { std::cout << "Base1 destructor" << std::endl; }
};
class Base2 {
public:
    Base2() { std::cout << "Base2 constructor" << std::endl; }
    ~Base2() { std::cout << "Base2 destructor" << std::endl; }
};
class Derived : public Base1, public Base2 {
public:
    Derived() { std::cout << "Derived constructor" << std::endl; }
    ~Derived() { std::cout << "Derived destructor" << std::endl; }
};
int main() {
    Derived d;
    return 0;
}

输出:

Base1 constructor
Base2 constructor
Derived constructor
Derived destructor
Base2 destructor
Base1 destructor
  • 虚继承
    • 调用顺序:虚基类的构造函数由最底层的派生类调用,且只调用一次。这是为了避免菱形继承中虚基类成员的重复拷贝。
    • 示例
class VirtualBase {
public:
    VirtualBase() { std::cout << "VirtualBase constructor" << std::endl; }
    ~VirtualBase() { std::cout << "VirtualBase destructor" << std::endl; }
};
class Derived1 : virtual public VirtualBase {
public:
    Derived1() { std::cout << "Derived1 constructor" << std::endl; }
    ~Derived1() { std::cout << "Derived1 destructor" << std::endl; }
};
class Derived2 : virtual public VirtualBase {
public:
    Derived2() { std::cout << "Derived2 constructor" << std::endl; }
    ~Derived2() { std::cout << "Derived2 destructor" << std::endl; }
};
class FinalDerived : public Derived1, public Derived2 {
public:
    FinalDerived() { std::cout << "FinalDerived constructor" << std::endl; }
    ~FinalDerived() { std::cout << "FinalDerived destructor" << std::endl; }
};
int main() {
    FinalDerived fd;
    return 0;
}

输出:

VirtualBase constructor
Derived1 constructor
Derived2 constructor
FinalDerived constructor
FinalDerived destructor
Derived2 destructor
Derived1 destructor
VirtualBase destructor

在虚继承中,由于虚基类的构造由最底层派生类负责,所以要注意在最底层派生类的构造函数初始化列表中正确调用虚基类的构造函数,以保证虚基类成员的正确初始化。