以下是 C++ 常见八股文(十四):
一、C++ 中的智能指针高级用法(Advanced Usage of Smart Pointers)
-
解释 unique_ptr、shared_ptr 和 weak_ptr 的循环引用问题及解决方法
-
循环引用问题:
- 当使用
shared_ptr
进行相互引用时,可能会导致循环引用问题。例如,两个对象相互持有对方的shared_ptr
,这会使得它们的引用计数永远不为零,即使没有外部引用指向它们,也不会被自动释放内存,从而造成内存泄漏。
- 当使用
-
解决方法:
- 使用
weak_ptr
来打破循环引用。weak_ptr
是一种弱引用,它不会增加对象的引用计数。当需要访问被weak_ptr
指向的对象时,可以通过调用lock()
方法来获取一个shared_ptr
,如果对象仍然存在,则可以正常访问;如果对象已经被释放,则返回一个空的shared_ptr
。例如:#include <iostream> #include <memory> class B; class A { public: std::shared_ptr<B> ptrB; ~A() { std::cout << "A destroyed." << std::endl; } }; class B { public: std::shared_ptr<A> ptrA; ~B() { std::cout << "B destroyed." << std::endl; } }; int main() { { std::shared_ptr<A> a = std::make_shared<A>(); std::shared_ptr<B> b = std::make_shared<B>(); a->ptrB = b; b->ptrA = a; } std::cout << "End of scope." << std::endl; return 0; }
在这个例子中,由于
A
和B
相互持有对方的shared_ptr
,导致它们在离开作用域时不会被自动释放。如果将其中一个改为weak_ptr
,就可以解决这个问题:class B; class A { public: std::shared_ptr<B> ptrB; ~A() { std::cout << "A destroyed." << std::endl; } }; class B { public: std::weak_ptr<A> ptrA; ~B() { std::cout << "B destroyed." << std::endl; } }; int main() { { std::shared_ptr<A> a = std::make_shared<A>(); std::shared_ptr<B> b = std::make_shared<B>(); a->ptrB = b; b->ptrA = a; } std::cout << "End of scope." << std::endl; return 0; }
-
如何自定义删除器(deleter)与智能指针结合使用?
- 有时候需要为智能指针指定自定义的删除器,特别是在管理资源不是简单的
delete
操作时。例如,管理文件句柄、网络连接等资源时,可能需要进行特定的清理操作。 - 对于
unique_ptr
和shared_ptr
,可以通过模板参数传递自定义删除器。例如:void customDeleter(int* ptr) { std::cout << "Custom deleter called." << std::endl; delete ptr; } int main() { std::unique_ptr<int, decltype(customDeleter*)> ptr(new int(10), customDeleter); return 0; }
在这个例子中,为
unique_ptr
指定了一个自定义的删除器函数customDeleter
。对于shared_ptr
,也可以类似地传递自定义删除器:struct CustomDeleter { void operator()(int* ptr) const { std::cout << "Custom deleter for shared_ptr called." << std::endl; delete ptr; } }; int main() { std::shared_ptr<int> ptr(new int(10), CustomDeleter()); return 0; }
- 有时候需要为智能指针指定自定义的删除器,特别是在管理资源不是简单的
- 使用
-
二、C++ 中的模板别名与模板参数推导(Template Aliases and Template Parameter Deduction)
-
解释模板别名(using)的作用及使用场景
-
作用:
- 模板别名可以为复杂的模板类型提供一个更简洁易读的名称。它允许程序员为模板类型定义一个新的名称,使得代码更加清晰和易于维护。
-
使用场景:
- 简化复杂的模板类型:当模板类型非常复杂时,使用模板别名可以使代码更易于理解。例如,对于一个包含多个模板参数的容器类型,可以定义一个模板别名来简化其使用。
- 提高代码的可维护性:如果模板类型的定义发生变化,只需要修改模板别名的定义,而不需要在整个代码中进行大量的修改。
- 增强代码的可读性:模板别名可以为特定的模板类型赋予一个有意义的名称,提高代码的可读性。例如,可以为一个特定的模板类型定义一个别名,如
MyContainerType
,使得代码更易于理解其用途。
-
例如:
template <typename T> using MyContainer = std::vector<T>; int main() { MyContainer<int> vec; return 0; }
-
深入探讨模板参数推导在复杂模板场景下的工作原理
- 在复杂模板场景下,模板参数推导可能会变得更加复杂。例如,当模板函数接受多个模板参数,或者模板参数是另一个模板类型时,编译器需要根据函数调用的实参来推导模板参数。
- 对于函数模板,编译器会根据实参的类型来推导模板参数。如果实参的类型与模板参数的类型不完全匹配,编译器会尝试进行类型转换,以找到最适合的模板参数。例如:
template <typename T, typename U> void func(T t, U u) { // 函数体 } int main() { func(10, 20.5); return 0; }
在这个例子中,编译器会根据实参
10
和20.5
的类型推导出模板参数T
为int
,U
为double
。 - 对于类模板,模板参数推导通常发生在类模板的构造函数中。如果构造函数的参数类型与类模板的参数类型不完全匹配,编译器也会尝试进行类型转换,以找到最适合的模板参数。例如:
template <typename T> class MyClass { public: MyClass(T t) { // 构造函数体 } }; int main() { MyClass obj(10); return 0; }
在这个例子中,编译器会根据实参
10
的类型推导出模板参数T
为int
。
-
三、C++ 中的移动语义与完美转发(Move Semantics and Perfect Forwarding)
-
深入理解移动语义在复杂对象中的应用
- 移动语义不仅适用于简单类型,还可以在复杂对象中发挥重要作用。例如,对于包含动态分配内存的类、容器类等,移动语义可以大大提高性能。
- 当一个对象被移动时,资源的所有权从源对象转移到目标对象,源对象通常被设置为一个有效但可确定的状态。例如,对于一个包含动态分配数组的类:
class MyComplexClass { private: int* data; public: MyComplexClass() : data(new int[10]) {} ~MyComplexClass() { delete[] data; } MyComplexClass(MyComplexClass&& other) noexcept : data(other.data) { other.data = nullptr; } MyComplexClass& operator=(MyComplexClass&& other) noexcept { if (this!= &other) { delete[] data; data = other.data; other.data = nullptr; } return *this; } };
在这个例子中,移动构造函数和移动赋值运算符实现了资源的转移,避免了不必要的内存复制。
-
解释完美转发的概念及在函数模板中的实现
-
概念:
- 完美转发是指在函数模板中,将参数原封不动地转发给另一个函数,保持参数的左值 / 右值属性和 const/volatile 修饰符。这样可以确保函数模板能够正确地将参数转发给其他函数,而不会丢失参数的原始属性。
-
实现:
- 在 C++ 中,可以使用
std::forward
来实现完美转发。例如:template <typename T> void func(T&& arg) { anotherFunc(std::forward<T>(arg)); }
在这个例子中,函数模板
func
接受一个参数arg
,并使用std::forward
将其转发给另一个函数anotherFunc
,实现了完美转发。
- 在 C++ 中,可以使用
-
四、C++ 中的常量表达式与编译期计算(Constant Expressions and Compile-Time Computation)
-
介绍常量表达式在 C++ 中的重要性及使用场景
-
重要性:
- 常量表达式在 C++ 中具有重要的作用,它可以在编译期进行计算,避免了运行时的开销。常量表达式可以用于定义数组大小、初始化静态成员变量、作为模板参数等。
-
使用场景:
- 数组大小定义:可以使用常量表达式来定义数组的大小,这样可以在编译期确定数组的大小,避免了动态分配内存的开销。例如:
constexpr int size = 10; int arr[size];
- 静态成员变量初始化:可以使用常量表达式来初始化静态成员变量,这样可以在编译期确定静态成员变量的值,避免了运行时的初始化开销。例如:
class MyClass { public: static constexpr int value = 10; };
- 模板参数:可以使用常量表达式作为模板参数,这样可以在编译期根据不同的参数值生成不同的代码。例如:
template <int N> class MyTemplate { public: // 模板类的实现 }; int main() { MyTemplate<10> obj; return 0; }
-
如何在 C++ 中进行更复杂的编译期计算?
- C++ 中可以使用模板元编程技术进行更复杂的编译期计算。例如,可以使用模板递归、模板特化等技术来实现编译期的数学计算、类型推导等功能。
- 例如,计算斐波那契数列的编译期版本:
template <int N> struct Fibonacci { enum { value = Fibonacci<N - 1>::value + Fibonacci<N - 2>::value }; }; template <> struct Fibonacci<0> { enum { value = 0 }; }; template <> struct Fibonacci<1> { enum { value = 1 }; };
在这个例子中,使用模板递归实现了编译期计算斐波那契数列。
- 数组大小定义:可以使用常量表达式来定义数组的大小,这样可以在编译期确定数组的大小,避免了动态分配内存的开销。例如:
-
喜欢的同学可以点点关注!这个系列将会持续更新!
标签:std,八股文,int,C++,程序员,参数,shared,ptr,模板 From: https://blog.csdn.net/a915227127/article/details/142636650