商汤C++面经

1. C++14有啥新特性
C++14的主要特性可以分为三个领域:Lambda函数、constexpr和类型推导。

Lambda函数
C++14的泛型Lambda使编写如下语句成为可能:
auto lambda = [](auto x, auto y) {return x + y;};

而另一方面,C++11要求Lambda参数使用具体的类型声明,比如:
auto lambda = [](int x, int y) {return x + y;};

此外,新标准中的std::move函数可用于捕获Lambda表达式中的变量,这是通过移动对象而非复制或引用对象实现的:
std::unique_ptr ptr(new int(10));auto lambda = [value = std::move(ptr)] {return *value;};

constexpr
在C++11中,使用constexpr声明的函数可以在编译时执行,生成一个值,用在需要常量表达式的地方,比如作为初始化模板的整形参数。C++11的constexpr函数只能包含一个表达式,C++14放松了这些限制,支持诸如if 和switch等条件语句,支持循环,其中包括基于区间(range)的for 循环。

类型推导
C++11仅支持Lambda函数的类型推导,C++14对其加以扩展,支持所有函数的返回类型推导:
auto DeducedReturnTypeFunction();

因为C++14是强类型语言,有些限制需要考虑:
如果一个函数的实现中有多个返回语句,这些语句一定要推导出同样的类型。
返回类型推导可以用在前向声明中,但是在使用它们之前,翻译单元中必须能够得到函数定义。
返回类型推导可以用在递归函数中,但是递归调用必须以至少一个返回语句作为先导,以便编译器推导出返回类型。

C++14带来的另一个类型推导方面的改进是decltype(auto)语法,它支持使用与auto同样的机制计算给定表达式的类型。auto和 decltype在C++11中就已经出现了,但是它们在推导类型时使用了不同的机制,这可能会产生不同的结果。

C++14中的其他改变包括可以声明变量模板,支持使用0b或0B前缀来声明二进制字面常量。
2、C++17新特性
fold expression
C++11增加了一个新特性可变模版参数(variadic template),它可以接受任意个模版参数在参数包中,参数包是三个点…,它不能直接展开,需要通过一些特殊的方法才能展开,导致在使用的时候有点难度。现在C++17解决了这个问题,让参数包的展开变得容易了,Fold expression就是方便展开参数包的。

fold expression的语义
fold expression有4种语义:

unary right fold (pack op …)
unary left fold (… op pack)
binary right fold (pack op … op init)
binary left fold (init op … op pack)
其中pack代表变参,比如args,op代表操作符,fold expression支持32种操作符:

      • / % ^ & | = < > << >> += -= = /= %= ^= &= |= <<= >>= == != <= >= && || , . ->*

constexpr if
constexpr标记一个表达式或一个函数的返回结果是编译期常量,它保证函数会在编译期执行。相比模版来说,实现编译期循环或递归,C++17中的constexpr if会让代码变得更简洁易懂。比如实现一个编译期整数加法:
template
constexpr int sum()
{
return N;
}

template <int N, int N2, int… Ns>
constexpr int sum()
{
return N + sum<N2, Ns…>();
}
C++17之前你可能需要像上面这样写,但是现在你可以写更简洁的代码了。
template <int N, int… Ns>
constexpr auto sum17()
{
if constexpr (sizeof…(Ns) == 0)
return N;
else
return N + sum17<Ns…>();
}

constexpr lambda
constexpr lambda其实很简单,它的意思就是可以在constexpr 函数中用lambda表达式了,这在C++17之前是不允许的。这样使用constexpr函数和普通函数没多大区别了,使用起来非常舒服。下面是constexpr lambda的例子:
template
constexpr auto func(I i) {
//use a lambda in constexpr context
return [i](auto j){ return i + j; };
}
constexpr if和constexpr lambda是C++17提供的非常棒的特性

string_view
string_view的基本用法
C++17中的string_view是一个char数据的视图或者说引用,它并不拥有该数据,是为了避免拷贝,因此使用string_view可以用来做性能优化。你应该用string_view来代替const char和const string了。string_view的方法和string类似
const char* data = “test”;
std::string_view str1(data, 4);
std::cout<<str1.length()<<’\n’; //4
if(data==str1)
std::cout<<“ok”<<’\n’;
const std::string str2 = “test”;
std::string_view str3(str2, str2.size());
构造string_view的时候用char*和长度来构造,这个长度可以自由确定,它表示string_view希望引用的字符串的长度。因为它只是引用其他字符串,所以它不会分配内存,不会像string那样容易产生临时变量。
string_view的生命周期

由于string_vew并不拥有锁引用的字符串,所以它也不会去关注被引用字符串的生命周期,用户在使用的时候需要注意,不要将一个临时变量给一个string_view,那样会导致string_view引用的内容也失效
std::string_view str_v;
{
std::string temp = “test”;
str_v = {temp};
}
这样的代码是有问题的,因为出了作用域之后,string_view引用的内容已经失效了。
总结:fold expression为了简化可变模板参数的展开,让可以模板参数的使用变得更简单直观;constexpr if让模板具备if-else功能,非常强大。它也避免了写冗长的enable_if代码,让代码变得简洁易懂了;string_view则是用来做性能优化的,应该用它来代替const char和const string。
3、C++20新特性
一门编程语言的变化大致上是应该分两个层面,一个是语言层面,即增加了语言新特性;二是库层面,其实就是各种“语法糖”等,它们是在语言层面的基础上扩展、实现出来的,使用起来更“接地气”。
从目前的资料来看,C++20的变化(以笔者的判断,按“改变度”从大到小)主要体现在:
module:module的引入,几乎是革命性的,它同时增加了import,export两个关键字,使C++可以摆脱C中“重复定义”,函数定义需要写两遍等历史包袱,变得“现代”起来。
concept(包括require):大量模板的使用,真的很有必要对模板参数的类型做一定的约束,concept就是为此目的,其实,C#早就引入了where,现在C++补上了这一部分,对模板参数的约束比以前表达得简洁多了。
小变化:
Coroutines:“并行”执行的一种方法,作为多线程的一种替代,特别适合“慢速”IO和“高速”计算并行的场合。Coroutines的概念也是早就有了,一些语言相继引入,之前boost库中实现了Coroutine的方法,C++11中实现了promise,现在C++引入了co_await、co_yeild、co_return这样的关键字,终于在语言层面实现了Coroutine。
Range,span:C++库中大量的集合操作,如果有表达“范围“的操作,一般采用 “映射函数”+新建集合的方法,现在有了range,span,view等概念,并且有一个range adaptor(|),此时表达就简洁多了。
3-way comparasion:C++定义了一个新的有趣的比较符:<=>,它形似飞船,因而也称作“spaceship operator”。集合操作中很常用的操作是“比大小”,<=>提供了一种更简洁的表达方式。
consteval, constinit:这两个关键字与constexpr有点类似,都是为了提高运行时速度而在编译时做处理。
增强:
Feature testing:定义了几个新的attributes,如likely, no_unique_address等,以及一些宏,可用于版本、功能的检测。
新的线程支持:增加了jthread线程类和semaphore同步类等新的特性,与jthread相适应,增加线程外通知机制,总体感觉有一定用途,但不一定是必须的或自己也可以实现简化版。
source_location:新增加< source_location>,它被划分为utility,主要为了信息跟踪(调试),而代替__FILE__,__LINE__等。
text format:新的格式化输出表达方式,感觉是C#输出方式的“模拟”。
时间库增强:主要是增加了时区(time zone)和日历(calendar)方面的计算、判断等。
计算(numeric)增强:增加了一些计算类(宏),endian判断支持以及按bit转换等功能
Synchronized output:增加了basic_syncbuf、basic_osyncstream模板类以及特化的类,它们被定义在中,在不同线程中,向它们的输出不会再相互干扰了。
4、lambda表达式是如何实现的
在这里插入图片描述
5、shared_ptr
智能指针的行为类似常规指针,重要的区别是它负责自动释放所指的对象。C++11标准库提供的这两种智能指针的区别在于管理底层指针的方式:shared_ptr允许多个指针指向同一个对象;unique_ptr则"独占"所指向的对象。C++11标准库还定义了一个名为weak_ptr的辅助类,它是一种弱引用,指向shared_ptr所管理的对象。这三种类型都定义在memory头文件中。智能指针是模板类而不是指针。类似vector,智能指针也是模板,当创建一个智能指针时,必须提供额外的信息即指针可以指向的类型。默认初始化的智能指针中保存着一个空指针。智能指针的使用方式与普通指针类似。解引用一个智能指针返回它指向的对象。如果在一个条件判断中使用智能指针,效果就是检测它是否为空。
智能指针实质就是重载了->和
操作符的类,由类来实现对内存的管理,确保即使有异常产生,也可以通过智能指针类的析构函数完成内存的释放。
shared_ptr的类型转换不能使用一般的static_cast,这种方式进行的转换会导致转换后的指针无法再被shared_ptr对象正确的管理。应该使用专门用于shared_ptr类型转换的 static_pointer_cast() , const_pointer_cast() 和dynamic_pointer_cast()。
使用shared_ptr避免了手动使用delete来释放由new申请的资源,标准库也引入了make_shared函数来创建一个shared_ptr对象,使用shared_ptr和make_shared,你的代码里就可以使new和delete消失,同时又不必担心内存的泄露。shared_ptr是一个模板类。
C++开发处理内存泄漏最有效的办法就是使用智能指针,使用智能指针就不会担心内存泄露的问题了,因为智能指针可以自动删除分配的内存。
智能指针是指向动态分配(堆)对象指针,用于生存期控制,能够确保自动正确的销毁动态分配的对象,防止内存泄露。它的一种通用实现技术是使用引用计数。每次使用它,内部的引用计数加1,每次析构一次,内部引用计数减1,减为0时,删除所指向的堆内存。
每一个shared_ptr的拷贝都指向相同的内存。在最后一个shared_ptr析构的时候, 内存才会被释放。
可以通过构造函数、赋值函数或者make_shared函数初始化智能指针。
shared_ptr基于”引用计数”模型实现,多个shared_ptr可指向同一个动态对象,并维护一个共享的引用计数器,记录了引用同一对象的shared_ptr实例的数量。当最后一个指向动态对象的shared_ptr销毁时,会自动销毁其所指对象(通过delete操作符)。
shared_ptr的默认能力是管理动态内存,但支持自定义的Deleter以实现个性化的资源释放动作。
最安全的分配和使用动态内存的方法是调用一个名为make_shared的标准库函数。此函数在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr。当要用make_shared时,必须指定想要创建的对象的类型,定义方式与模板类相同。在函数名之后跟一个尖括号,在其中给出类型。例如,调用make_shared时传递的参数必须与string的某个构造函数相匹配。如果不传递任何参数,对象就会进行值初始化。
通常用auto定义一个对象来保存make_shared的结果。
当进行拷贝或赋值操作时,每个shared_ptr都会记录有多少个其它shared_ptr指向相同的对象。
可以认为每个shared_ptr都有一个关联的计数器,通常称其为引用计数(reference count)。无论何时拷贝一个shared_ptr,计数器都会递增。例如,当用一个shared_ptr初始化另一个shared_ptr,或将它作为参数传递给一个函数以及作为函数的返回值时,它所关联的计数器就会递增。当给shared_ptr赋予一个新值或是shared_ptr被销毁(例如一个局部的shared_ptr离开其作用域)时,计数器就会递减。一旦一个shared_ptr的计数器变为0,它就会自动释放自己所管理的对象。
当指向一个对象的最后一个shared_ptr被销毁时,shared_ptr类会自动销毁此对象。它是通过另一个特殊的成员函数析构函数(destructor)来完成销毁工作的。类似于构造函数,每个类都有一个析构函数。就像构造函数控制初始化一样,析构函数控制此类型的对象销毁时做什么操作。shared_ptr的析构函数会递减它所指向的对象的引用计数。如果引用计数变为0,shared_ptr的析构函数就会销毁对象,并释放它占用的内存。
如果将shared_ptr存放于一个容器中,而后不再需要全部元素,而只使用其中一部分,要记得用erase删除不再需要的那些元素。
6、为什么不能返回局部的引用?返回值是栈上的值,函数结束,栈被系统回收,内存的值就不存在了。
7、push_back和emplace_back的区别?emplace_back优于push_back,能就地通过参数构造对象,不需要拷贝和移动内存,提升容器插入性能
8、traits??

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值