分享C++程序员面试八股文(十四)

以下是 C++ 常见八股文(十四):

一、C++ 中的智能指针高级用法(Advanced Usage of Smart Pointers)

  1. 解释 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;
        }

        在这个例子中,由于AB相互持有对方的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_ptrshared_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)

  1. 解释模板别名(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;
        }

        在这个例子中,编译器会根据实参1020.5的类型推导出模板参数TintUdouble

      • 对于类模板,模板参数推导通常发生在类模板的构造函数中。如果构造函数的参数类型与类模板的参数类型不完全匹配,编译器也会尝试进行类型转换,以找到最适合的模板参数。例如:
        template <typename T>
        class MyClass {
        public:
            MyClass(T t) {
                // 构造函数体
            }
        };
        
        int main() {
            MyClass obj(10);
            return 0;
        }

        在这个例子中,编译器会根据实参10的类型推导出模板参数Tint

三、C++ 中的移动语义与完美转发(Move Semantics and Perfect Forwarding)

  1. 深入理解移动语义在复杂对象中的应用

    • 移动语义不仅适用于简单类型,还可以在复杂对象中发挥重要作用。例如,对于包含动态分配内存的类、容器类等,移动语义可以大大提高性能。
    • 当一个对象被移动时,资源的所有权从源对象转移到目标对象,源对象通常被设置为一个有效但可确定的状态。例如,对于一个包含动态分配数组的类:
      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++ 中的常量表达式与编译期计算(Constant Expressions and Compile-Time Computation)

  1. 介绍常量表达式在 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 };
          };

          在这个例子中,使用模板递归实现了编译期计算斐波那契数列。

 喜欢的同学可以点点关注!这个系列将会持续更新!

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值