C++面试

目录

1.shared_ptr 和 unique_ptr 区别,以及性能对比

2.weak_ptr 及其作用

3.shared_ptr 是线程安全的吗

4.讲讲多态及实现机制

5.虚基类

6.多继承的时候,虚函数表指针怎么存


1.shared_ptr 和 unique_ptr 区别,以及性能对比

shared_ptr 和 unique_ptr 都是 C++11 引入的智能指针类型,它们都可以管理动态分配的内存资源,自动回收资源并避免内存泄漏和野指针问题。它们之间的关键区别在于内部的资源管理方式和所有权机制。

shared_ptr 是一种共享所有权的智能指针,多个 shared_ptr 对象可以同时拥有对同一个空间的控制权,通过引用计数来协调所有权的转移和回收。shared_ptr 通过维护一个引用计数器,来确保只有最后一个引用该对象的 shared_ptr 被销毁时才会释放被管理的内存资源。shared_ptr 有更强的语义表达能力且使用起来比较方便,但是带有一定的性能开销。

unique_ptr 是一种独占所有权的智能指针,每个 unique_ptr 对象都有独立的对象所有权,不能与其他 unique_ptr 对象共享同一份资源。unique_ptr 可以通过 std::move() 函数来实现所有权的移动,从而实现资源的转移和释放。unique_ptr 没有引用计数器,因此没有额外的内存开销,也可以通过 std::make_unique() 函数来一次性地分配和初始化对象和其所需要的资源,具有更好的性能和资源利用率。

就性能而言,由于 shared_ptr 在维护引用计数时需要进行原子操作,因此存在一定的性能开销。而 unique_ptr 没有引用计数器,所以通常比 shared_ptr 具有更好的性能。在实际使用中,应该根据具体场景和需求综合考虑它们的优劣和适用性。

需要注意的是,shared_ptr 和 unique_ptr 都是由 std 命名空间提供的模板类,使用时需要先包含对应的头文件。同时,在使用智能指针时还需要注意其生命周期、所有权和内存管理等问题,避免出现资源泄漏和安全问题。

2.weak_ptr 及其作用

weak_ptr 也是 C++11 引入的智能指针类型,它与 shared_ptr 配合使用,可以协助解决 shared_ptr 在处理循环引用时可能出现的内存泄漏问题。

在使用 shared_ptr 时,如果两个或多个对象彼此相互持有 shared_ptr,那么就会出现循环引用的情况。这种情况下,由于 shared_ptr 的引用计数无法归零,会导致被管理内存的资源无法释放,从而造成内存泄漏。为了避免这种问题,可以使用 weak_ptr 来解决循环引用的影响。

weak_ptr 是一种弱引用的智能指针,与 shared_ptr 不同,它只是观察和访问被 shared_ptr 管理的对象,而不影响对象本身的拥有权和内存管理。weak_ptr 可以通过 shared_ptr 创建和绑定,也可以通过 weak_ptr 相互转换,但是不能通过 weak_ptr 直接初始化对象。

当需要使用 weak_ptr 访问被 shared_ptr 管理的对象时,需要先将 weak_ptr 转换为 shared_ptr,然后再访问对象。如果该对象已经被销毁,那么转换结果将返回空的 shared_ptr 指针。这种机制可以帮助程序员在访问对象时判断其是否已经被销毁,避免了悬垂指针的出现,提高了程序的安全性和健壮性。

总的来说,weak_ptr 主要有以下两个作用:

  1. 解决循环引用:在使用 shared_ptr 时,如果存在循环引用,可以使用 weak_ptr 来打破这种引用关系,避免内存泄漏的发生。

  2. 安全访问被管理对象:当需要访问被 shared_ptr 管理的对象时,可以通过 weak_ptr 转换为 shared_ptr,并检测其是否已经被销毁,避免了访问已被释放的空指针的危险。

需要注意的是,weak_ptr 的生命周期应该在被观察对象的生命周期之外,避免出现悬垂指针和使用已被销毁的对象的情况。同时,使用 weak_ptr 时,需要先进行转换为 shared_ptr,并进行有效性检查,保证程序的稳定性和可靠性。

3.shared_ptr 是线程安全的吗

shared_ptr 在 C++11 STL 中是线程安全的,因为它是通过原子操作来维护引用计数的。这意味着在多个线程同时访问同一个 shared_ptr 对象时,不会发生引用计数的错误和内存泄漏等问题。

在实现上,shared_ptr 使用了一种叫做引用记数(reference counting)的技术来跟踪对象被共享的次数。每当一个新的 shared_ptr 对象指向同一块内存区域时,其引用计数就会加一;相反,如果一个 shared_ptr 对象销毁或者其内部指针指向别处时,其引用计数就会减一。当引用计数降为零时,shared_ptr 会自动销毁所管理的对象并释放其占用的内存。

由于引用计数是一个关键值,在增加或减少计数时需要保证线程安全。C++11 STL 使用了一些原子操作来实现 shared_ptr 内部的线程安全,比如 std::atomic_fetch_add 和 std::atomic_fetch_sub 等函数,这些函数保证了在多个线程同时进行引用计数的修改时,可以保证最终结果的正确和准确。

需要注意的是,在使用 shared_ptr 的过程中,需要特别注意内存泄漏和资源争夺等问题,合理地使用智能指针,并结合 RAII 编程原则,才能保证程序的健壮性和稳定性。同时,对于一些复杂的场景和需求,可以考虑使用其他类型的智能指针或者手动管理内存等方式。

4.讲讲多态及实现机制

多态(polymorphism)是 C++ 中最重要的特性之一,它允许派生类对象可以用作基类对象使用,实现了继承层次结构中的灵活性和可扩展性。

在 C++ 中,多态的实现机制主要有两种:

  1. 静态多态(static polymorphism),也称为编译期多态,指使用函数重载和模板等技术实现的多态。静态多态的实现在编译期间完成,因此具有高效、类型安全和优化等优点,但是需要在编译时确定函数或模板的类型参数。例如:
// 函数重载 
void print(int x) 
{ 
    std::cout << "int: " << x << std::endl; 
} 
void print(double x) 
{ 
    std::cout << "double: " << x << std::endl;
} 
// 模板
template void print(T x)
{ 
    std::cout << "value: " << x << std::endl; 
}

在上面的例子中,通过函数重载和模板的方式来实现了静态多态,函数或模板的行为会根据参数的类型来进行选择和调用。

  1. 动态多态(dynamic polymorphism),也称为运行时多态,指使用虚函数和指针等技术实现的多态。动态多态的实现在运行时期间完成,因此具有灵活、可扩展和可维护等优点,但是需要消耗额外的运行时开销。例如:
class Shape {
public: 
    virtual double area() const = 0; 
}; 
class Circle : public Shape { 
public: 
    Circle(double r) : radius(r) {} 
    double area() const override { 
        return 3.14 * radius * radius; 
    } 
private: 
    double radius; 
}; 
class Rectangle : public Shape { 
public: 
    Rectangle(double w, double h) : width(w), height(h) {} 
    double area() const override { 
        return width * height; 
    } 
private:
    double width, height; 
}; 
int main() { 
    Shape* shape1 = new Circle(2.0); 
    Shape* shape2 = new Rectangle(3.0, 4.0); 
    std::cout << "Circle area: " << shape1->area() << std::endl; // 输出 Circle 的面积 
    std::cout << "Rectangle area: " << shape2->area() << std::endl; // 输出 Rectangle 的面积                                                                         
    delete shape1; 
    delete shape2; 
    return 0; 
}

在上面的例子中,通过使用虚函数和基类指针的方式来实现了动态多态。派生类可以重写基类的虚函数进行不同的行为,同时基类指针可以用来表示其派生类对象,并在运行时期间根据实际的对象类型来调用对应的虚函数。

总之,多态是 C++ 中一个非常强大的特性,通过静态多态和动态多态的实现机制,可以在继承层次结构中进行灵活和扩展的设计,并实现更加高效、安全、可读和可维护的程序。

5.虚基类

虚基类是指在多重继承中,为了避免出现二义性而采用的一种技术。当一个类派生出多个直接派生类,而这些直接派生类都共享一个基类时,就会出现二义性问题。为了解决这个问题,可以在基类前加上关键字 "virtual",将这个基类声明为虚基类。

声明为虚基类后,无论该虚基类在继承体系中出现多少次,派生类只会保留一个虚基类的实例。这样就能够保证所有派生类对该虚基类所包含的数据成员和成员函数只有一份拷贝,从而避免了数据重复和访问冲突的问题。

虚基类的语法格式如下:

class Base1 {...};
 class Base2 : virtual public Base1 {...};
 class Base3 : virtual public Base1 {...}; 
class Derived : public Base2, public Base3 {...};

其中,Base2 和 Base3 都继承自虚基类 Base1,Derived 类再以多重继承方式同时派生自 Base2 和 Base3。这样,Derived 类对 Base1 的访问将只有一份实例,避免了数据冗余和访问冲突的问题。

6.多继承的时候,虚函数表指针怎么存

在C++中,一个类如果包含虚函数,那么编译器会在这个类的对象中添加一个指向虚函数表(Virtual Function Table,简称 vtable)的指针。而对于多继承的情况,则会存在多个虚函数表,因此每个基类都需要在派生类中维护自己的虚函数表。

具体来说,对于一个包含多个基类的派生类,它的内存布局一般是这样的:

+-----------------------------+

|   派生类成员变量和函数   |

+-----------------------------+

|  基类A的成员变量和虚函数表 |

+-----------------------------+

|  基类B的成员变量和虚函数表 |

+-----------------------------+

| ............................ |

+-----------------------------+

可以看到,每个基类在派生类中都有自己的一份虚函数表以及相应的指针,用于正确地分发虚函数调用。当派生类通过基类指针或引用调用一个虚函数时,编译器会根据虚函数表指针所指向的位置找到正确的虚函数,并进行调用

你可能感兴趣的:(面经,c++,面试,开发语言,服务器,职场和发展)