C++面试常见问题 - 知乎
智能指针:
智能指针(Smart Pointers)是一种用于管理动态内存的数据结构,通常用于C++和某些其他编程语言中。它们提供了更安全和方便的内存管理方式,帮助减少内存泄漏和悬垂指针等问题。智能指针是与RAII(资源获取即初始化)编程原则紧密相关的,因此它们确保在离开作用域时自动释放分配的内存。
主要的智能指针类型包括:
-
std::shared_ptr:共享指针,允许多个智能指针共享相同的资源。资源在最后一个引用离开作用域时释放。
-
std::unique_ptr:唯一指针,确保只有一个指针可以访问分配的资源。资源在唯一指针离开作用域时释放。
-
std::weak_ptr:弱指针,与shared_ptr一起使用,用于解决循环引用的问题。弱指针不增加引用计数,它只能用于监视资源的生存状态。
智能指针的好处包括:
-
自动内存管理:它们负责在资源不再需要时自动释放内存,减少了内存泄漏的风险。
-
减少悬垂指针:当资源被释放后,智能指针将确保不再引用它,从而避免了悬垂指针问题。
-
简化代码:它们可以减少显式的内存管理操作,使代码更清晰和安全。
-
支持多线程:某些智能指针类型(如
std::shared_ptr
)具有引用计数,可以用于多线程环境。
使用智能指针有助于提高C++程序的健壮性和可维护性。然而,开发者仍然需要小心避免循环引用,以确保资源的正确释放。
实现一个智能指针:
#include <iostream>template <typename T>
class SmartPointer {
private:T* ptr;public:// 构造函数SmartPointer(T* p = nullptr) : ptr(p) {}// 析构函数~SmartPointer() {delete ptr; // 在析构函数中释放资源}// 拷贝构造函数SmartPointer(const SmartPointer<T>& other) {ptr = new T(*other.ptr);}// 赋值运算符SmartPointer<T>& operator=(const SmartPointer<T>& other) {if (this == &other) {return *this;}delete ptr; // 释放当前资源ptr = new T(*other.ptr);return *this;}// 解引用操作符T& operator*() {return *ptr;}// 成员访问操作符T* operator->() {return ptr;}
};int main() {SmartPointer<int> sp1(new int(42));SmartPointer<int> sp2 = sp1; // 使用拷贝构造函数SmartPointer<int> sp3(new int(10));sp3 = sp2; // 使用赋值运算符std::cout << "Value from sp1: " << *sp1 << std::endl;std::cout << "Value from sp2: " << *sp2 << std::endl;std::cout << "Value from sp3: " << *sp3 << std::endl;return 0;
}
share_ptr是如何实现的
std::shared_ptr
是C++标准库中的智能指针,它允许多个std::shared_ptr
共享相同的资源,并在资源不再需要时自动释放。std::shared_ptr
的实现通常基于引用计数,它会记录资源被多少个std::shared_ptr
共享。以下是std::shared_ptr
的基本工作原理:
-
构造和拷贝构造:当您创建一个
std::shared_ptr
时,它将创建一个引用计数对象,该对象包含两部分信息:指向分配的资源的指针和一个引用计数。 -
引用计数:每当您拷贝一个
std::shared_ptr
(或将其作为参数传递给函数),引用计数会递增。当析构或赋值操作使引用计数为零时,资源会被释放。 -
资源释放:当
std::shared_ptr
的引用计数变为零,它将自动释放关联的资源。这是通过析构函数来实现的,当引用计数为零时,析构函数被调用,资源被释放。 -
复制和赋值:
std::shared_ptr
支持复制和赋值操作,使多个std::shared_ptr
可以共享相同的资源,而不会出现悬垂指针问题。当一个std::shared_ptr
离开作用域或不再需要资源时,它的引用计数会减少,直到资源不再被引用。 -
循环引用:
std::shared_ptr
存在循环引用的潜在问题。如果两个或多个std::shared_ptr
相互引用,它们的引用计数将永远不会变为零,导致资源泄漏。为了解决这个问题,C++11引入了std::weak_ptr
,允许弱引用资源,但不会增加引用计数,用于打破循环引用。
std::shared_ptr
的实现会维护引用计数并确保在没有引用时释放资源。这是通过使用std::shared_ptr
的析构函数来实现的,当最后一个std::shared_ptr
离开作用域或不再需要资源时,析构函数被调用,资源得到释放。引用计数的维护是线程安全的,这使得std::shared_ptr
适用于多线程环境。
实现一个完整的 shared_ptr
需要涉及引用计数、资源管理、拷贝构造、赋值运算符重载等复杂的细节。以下是一个非常基本的示例,展示了 shared_ptr
的基本思想,但不包括线程安全和其他重要功能。请注意,这只是教育目的的示例,实际使用时应使用C++标准库中的 std::shared_ptr
。
#include <iostream>template <typename T>
class SharedPtr {
public:// 构造函数SharedPtr(T* ptr) : data(ptr), ref_count(new int(1)) {}// 拷贝构造函数SharedPtr(const SharedPtr<T>& other) : data(other.data), ref_count(other.ref_count) {(*ref_count)++;}// 析构函数~SharedPtr() {if (--(*ref_count) == 0) {delete data;delete ref_count;}}// 赋值运算符SharedPtr<T>& operator=(const SharedPtr<T>& other) {if (this == &other) {return *this;}// 减少当前对象的引用计数if (--(*ref_count) == 0) {delete data;delete ref_count;}data = other.data;ref_count = other.ref_count;(*ref_count)++;return *this;}// 解引用操作符T& operator*() {return *data;}// 成员访问操作符T* operator->() {return data;}private:T* data;int* ref_count;
};int main() {SharedPtr<int> sp1(new int(42));SharedPtr<int> sp2 = sp1;SharedPtr<int> sp3(new int(10));sp3 = sp2;std::cout << "Value from sp1: " << *sp1 << std::endl;std::cout << "Value from sp2: " << *sp2 << std::endl;std::cout << "Value from sp3: " << *sp3 << std::endl;return 0;
}
这个示例演示了一个非线程安全的 SharedPtr
类,它能够跟踪引用计数并在引用计数减为零时释放资源。在实际应用中,您需要考虑线程安全、更复杂的功能,以及更多的边界情况,以确保正确和高效的资源管理。最好的选择是使用C++标准库中的 std::shared_ptr
,因为它已经经过充分测试和优化。
shared_ptr使用方法:
1. 构造函数:std::shared_ptr
可以使用多种构造函数创建,其中包括从原始指针、另一个 std::shared_ptr
、或者其他智能指针类型创建。
std::shared_ptr<int> sp1(new int(42)); // 使用原始指针创建
std::shared_ptr<int> sp2 = std::make_shared<int>(42); // 使用 make_shared 创建
std::shared_ptr<int> sp3 = sp1; // 使用拷贝构造函数创建
拷贝构造函数:用于创建一个新的 std::shared_ptr
,共享相同的资源。
std::shared_ptr<int> sp1(new int(42));
std::shared_ptr<int> sp2 = sp1; // 使用拷贝构造函数
赋值操作符:用于将一个 std::shared_ptr
赋值给另一个,共享相同的资源。
std::shared_ptr<int> sp1(new int(42));
std::shared_ptr<int> sp2;
sp2 = sp1; // 使用赋值操作符
reset 方法:用于重置 std::shared_ptr
,它可以释放资源并指向新的资源。
std::shared_ptr<int> sp1(new int(42));
sp1.reset(new int(10)); // 重置 shared_ptr,释放旧资源
use_count 方法:用于获取 std::shared_ptr
的引用计数。
std::shared_ptr<int> sp1(new int(42));
int count = sp1.use_count(); // 获取引用计数
get 方法:用于获取 std::shared_ptr
内部的原始指针。
std::shared_ptr<int> sp1(new int(42));
int* rawPtr = sp1.get(); // 获取原始指针
*operator 和 operator->**:允许通过 *
和 ->
操作符来访问资源。
std::shared_ptr<int> sp1(new int(42));
int value = *sp1; // 解引用操作符
int* rawPtr = sp1.get();
int value2 = *rawPtr; // 也可以通过原始指针访问
operator bool:用于检查 std::shared_ptr
是否为空(未指向任何资源)。
std::shared_ptr<int> sp1;
if (!sp1) {// shared_ptr 为空
}
这些方法和操作符使 std::shared_ptr
可以方便地管理资源和共享资源的所有权,同时自动处理引用计数和资源释放。
C++ 中的构造函数有几种:
在C++中,构造函数有几种不同的类型,主要分为以下几类:
-
默认构造函数(Default Constructor):
- 默认构造函数没有参数,用于创建对象的实例。
- 如果您没有为类定义构造函数,编译器会为您自动生成一个默认构造函数,但如果您自定义了任何构造函数,编译器将不再提供默认构造函数。
-
参数化构造函数(Parameterized Constructor):
- 参数化构造函数接受一个或多个参数,用于初始化对象的成员变量。
- 它允许您在创建对象时传递参数,以自定义对象的初始化。
-
拷贝构造函数(Copy Constructor):
- 拷贝构造函数接受同一类型的对象作为参数,用于创建一个新对象,新对象的值与原对象相同。
- 它在对象复制时被调用,通常在对象传递给函数或通过赋值操作时使用。
-
移动构造函数(Move Constructor)(C++11及以后):
- 移动构造函数用于将资源从一个对象“移动”到另一个对象,而不是进行复制。这提高了性能,特别是在处理动态分配内存等资源时。
-
复制构造函数和移动构造函数可以重载的版本:
- 复制构造函数和移动构造函数可以有多个版本,根据参数的不同来重载。例如,可以有一个接受常量引用和一个接受非常量引用的版本。
-
析构函数(Destructor):
- 析构函数没有参数,用于对象生命周期结束时清理资源。通常用于释放动态分配的内存、关闭文件、释放锁等操作。
-
委托构造函数(Delegating Constructor)(C++11及以后):
- 委托构造函数是一个构造函数调用另一个构造函数,以减少冗余代码。这允许您在一个构造函数中调用另一个构造函数来执行实际的初始化工作。
这些构造函数类型提供了不同的初始化和对象创建方式,根据您的需要,可以选择适合的构造函数类型。