c++11 新特性学习

本文详细介绍了C++11中的Lambda表达式和std::thread。Lambda表达式提供了简洁的匿名函数实现,允许捕获外部变量,可自定义返回类型和参数列表。std::thread则涉及线程的创建、管理和同步,包括join和detach操作。文中通过实例展示了如何使用Lambda表达式进行排序和std::thread创建多线程。
摘要由CSDN通过智能技术生成

1. lambda 表达式

C++ 11 Lambda表达式 - 滴水瓦 - 博客园

Lambda表达式完整的声明格式如下:

[capture list] (params list) mutable exception-> return type { function body }

各项具体含义如下

  1. capture list:捕获外部变量列表
  2. params list:形参列表
  3. mutable指示符:用来说用是否可以修改捕获的变量
  4. exception:异常设定
  5. return type:返回类型
  6. function body:函数体

此外,我们还可以省略其中的某些成分来声明“不完整”的Lambda表达式,常见的有以下几种:

序号格式
1[capture list] (params list) -> return type {function body}
2[capture list] (params list) {function body}
3[capture list] {function body}

其中:

  • 格式1声明了const类型的表达式,这种类型的表达式不能修改捕获列表中的值。
  • 格式2省略了返回值类型,但编译器可以根据以下规则推断出Lambda表达式的返回类型: (1):如果function body中存在return语句,则该Lambda表达式的返回类型由return语句的返回类型确定; (2):如果function body中没有return语句,则返回值为void类型。
  • 格式3中省略了参数列表,类似普通函数中的无参函数。

    讲了这么多,我们还没有看到Lambda表达式的庐山真面目,下面我们就举一个实例。

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

bool cmp(int a, int b)
{
    return  a < b;
}

int main()
{
    vector<int> myvec{ 3, 2, 5, 7, 3, 2 };
    vector<int> lbvec(myvec);

    sort(myvec.begin(), myvec.end(), cmp); // 旧式做法
    cout << "predicate function:" << endl;
    for (int it : myvec)
        cout << it << ' ';
    cout << endl;

    sort(lbvec.begin(), lbvec.end(), [](int a, int b) -> bool { return a < b; });   // Lambda表达式
    cout << "lambda expression:" << endl;
    for (int it : lbvec)
        cout << it << ' ';
}

2.  std::thread

头文件在 <thread>

构造函数:

default (1)
thread() noexcept;
initialization (2)
template <class Fn, class... Args>
explicit thread (Fn&& fn, Args&&... args);
copy [deleted] (3)
thread (const thread&) = delete;
move (4)
thread (thread&& x) noexcept;
  • (1). 默认构造函数,创建一个空的 thread 执行对象。
  • (2). 初始化构造函数,创建一个 thread对象,该 thread对象可被 joinable,新产生的线程会调用 fn 函数,该函数的参数由 args 给出。
  • (3). 拷贝构造函数(被禁用),意味着 thread 不可被拷贝构造。
  • (4). move 构造函数,move 构造函数,调用成功之后 x 不代表任何 thread 执行对象。
  • 注意:可被 joinable 的 thread 对象必须在他们销毁之前被主线程 join 或者将其设置为 detached.

使用实例:

实例一:

#include <iostream>
#include <utility>
#include <thread>
#include <chrono>
#include <functional>
#include <atomic>
 
void f1(int n)
{
    for (int i = 0; i < 5; ++i) {
        std::cout << "Thread " << n << " executing\n";
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
}
 
void f2(int& n)
{
    for (int i = 0; i < 5; ++i) {
        std::cout << "Thread 2 executing\n";
        ++n;
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
}
 
int main()
{
    int n = 0;
    std::thread t1; // t1 is not a thread
    std::thread t2(f1, n + 1); // pass by value
    std::thread t3(f2, std::ref(n)); // pass by reference
    std::thread t4(std::move(t3)); // t4 is now running f2(). t3 is no longer a thread
    t2.join();
    t4.join();
    std::cout << "Final value of n is " << n << '\n';
}

实例二:

#include <thread>
#include <iostream>
#include <mutex>

void hello_thread()
{
    std::cout << "hello thread" << std::endl;
}

int main(int argc, char* argv[])
{
    std::thread t1(hello_thread);
    
    t1.join();
    
    std::cout << "Main here" << std::endl;
    
    return 0
}

move 赋值操作

move (1)
thread& operator= (thread&& rhs) noexcept;
copy [deleted] (2)
thread& operator= (const thread&) = delete;
  • (1). move 赋值操作,如果当前对象不可 joinable,需要传递一个右值引用(rhs)给 move 赋值操作;如果当前对象可被 joinable,则 terminate() 报错。
  • (2). 拷贝赋值操作被禁用,thread 对象不可被拷贝

template< class T >
typename std::remove_reference<T>::type&& move( T&& t ) noexcept;

std::move返回的是一个右值引用的变量,而函数的作用是将t到另一对象有效率的资源传递,传递过后,资源t会被销毁。在实例一中的作用就是将线程t3的资源传递给t4,并销毁t3。
 

std::thread::join 与 std::thread::detach 的区别

        join会阻塞当前的线程,直到运行的线程结束,比如在main函数里面调用线程thread,那么main函数里面调用thread后,会先去执行thread中的代码逻辑,直到其结束,再去执行main函数里面的代码逻辑。调用join后,所有分配的资源都会被释放。在调用了join之后,*this就不会拥有任何线程了。 而detach不会阻塞主线程,主线程和子线程是同时运行的

其他成员函数

获取线程 ID。为 std::thread::id 类型值

检查线程是否可被 join。

Join 线程。

Detach 线程

Swap 线程 。

返回 native handle。

检测硬件并发特性。返回实现所支持的并发线程数

========================================================

1. explicit关键字 的作用就是防止类构造函数的隐式自动转换

  explicit ConfigurationFileResolver(
      const std::vector<std::string>& configuration_files_directories);

2. std::lround 返回最接近x的long int整数 eg: lround(15.2) -> 15, lround(15.8) -> 16
// 返回最接近x的整数,四舍五入
 

inline int RoundToInt(const float x) { return std::lround(x); }

3. decltype 可以从一个变量或表达式中得到类型(这里不是很明白)
// 在 C++11 中, auto可以和decltype一起用, 将auto放置在返回值类型上当做占位符, 不表达实际意思
// 在参数列表后添加 -> decltype( ), 这是后置返回类型, 代表返回的类型是 () 中的类型
// 在 C++14 中, 则可以不用 decltype

template <class T>
auto GetTimeImpl(const T& t, int) -> decltype(t.time()) {
  return t.time();
}

4.  std::bidirectional_iterator_tag 用于将迭代器的类别标识为双向迭代器

    using iterator_category = std::bidirectional_iterator_tag;

5. static 修饰 成员函数, 代表静态成员函数
  // 静态成员函数是属于类的, 不是属于对象的. 静态成员函数只能访问静态成员变量

  // 只有 NodeId 和 SubmapId 才可以当做 MapById 的key

  static int GetIndex(const NodeId& id) { return id.node_index; }

6. // c++11: override 关键字告诉编译器, 该函数应覆盖基类中的函数.
  // 如果该函数实际上没有覆盖任何函数, 则会导致编译器错误
  // 如果没加这个关键字 也没什么严重的error 只是少了编译器检查的安全性

  // Returns the time of the last added pose or Time::min() if no pose was added
  // yet.

  common::Time GetLastPoseTime() const override;


7. std::unique_ptr 是独享被管理对象指针所有权的智能指针
  // 它无法复制到其他 unique_ptr, 也无法通过值传递到函数,也无法用于需要副本的任何标准模板库 (STL) 算法
  // 只能通过 std::move() 来移动unique_ptr
  // std::make_unique 是 C++14 才有的特性

  std::unique_ptr<ImuTracker> imu_tracker_;               // 保存与预测当前姿态  


8. std::function 通用多态函数封装器
  // std::function 的实例能存储、复制及调用任何可调用 (Callable) 目标:
  // 如函数、 lambda表达式、 bind表达式或其他函数对象, 还有指向成员函数指针和指向数据成员指针.
  // 它也是对 C++ 中现有的可调用实体的一种类型安全的包裹(相对来说, 函数指针的调用不是类型安全的)

9. using in c++11: c++11 的using可以用于模板部分具体化

  // A callback which is called after local SLAM processes an accumulated
  // 'sensor::RangeData'. If the data was inserted into a submap, reports the
  // assigned 'NodeId', otherwise 'nullptr' if the data was filtered out.

  using LocalSlamResultCallback =
      std::function<void(int /* trajectory ID */, common::Time,
                         transform::Rigid3d /* local pose estimate */,
                         sensor::RangeData /* in local frame */,
                         std::unique_ptr<const InsertionResult>)>;  


10. 限域枚举 enum class

    enum class SensorType {
      RANGE = 0,
      IMU,
      ODOMETRY,
      FIXED_FRAME_POSE,
      LANDMARK,
      LOCAL_SLAM_RESULT
    };


11. reinterpret_cast 用于进行各种不同类型的指针之间、不同类型的引用之间以及指针和能容纳指针的整数类型之间的转换

  // MakeUniqueCairoSurfacePtr 生成一个指向cairo_surface_t数据的指针

  auto surface = MakeUniqueCairoSurfacePtr(
    // cairo_image_surface_create_for_data: 根据提供的像素数据创建surface, 返回指向新创建的surface的指针
    cairo_image_surface_create_for_data(
      reinterpret_cast<unsigned char*>(cairo_data->data()), kCairoFormat, width,
      height, expected_stride) );

12. std::array::data() 返回指向数组对象中第一个元素的指针

    problem.AddParameterBlock(C_submaps.at(submap_id_data.id).data(), 3);

13. 匿名命名空间, 作用域被限制在本文件内

namespace { }

14. static_cast关键字(编译时类型检查): static_cast < type-id > ( expression )
     * 该运算符把expression转换为type-id类型, 但没有运行时类型检查来保证转换的安全性
      (1)用于基本数据类型之间的转换, 如把int转换为char, 把int转换成enum,
      (2)把空指针转换成目标类型的空指针
      (3)把任何类型的表达式类型转换成void类型
      (4)用于类层次结构中父类和子类之间指针和引用的转换.

15. dynamic_cast关键字(运行时类型检查): dynamic_cast < type-id > ( expression )
        该运算符把 expression 转换成 type-id 类型的对象. Type-id必须是类的指针、类的引用或者void *
        如果type-id是类指针类型, 那么expression也必须是一个指针
        如果type-id是一个引用, 那么expression也必须是一个引用

        dynamic_cast主要用于类层次间的上行转换(子类到父类)和下行转换(父类到子类), 还可以用于类之间的交叉转换.
        在类层次间进行上行转换时, dynamic_cast和static_cast的效果是一样的;
        在进行下行转换时, dynamic_cast具有类型检查的功能, 比static_cast更安全.

16. std::lower_bound() 是在区间内找到第一个大于等于 value 的值的位置并返回, 如果没找到就返回 end() 位置
  // 在第四个参数位置可以自定义比较规则,在区域内查找第一个 **不符合** comp 规则的元素

  // 在imu数据队列中找到第一个时间上 大于等于 imu_tracker->time() 的数据的索引

 auto it = std::lower_bound(
      imu_data_.begin(), imu_data_.end(), imu_tracker->time(),
      [](const sensor::ImuData& imu_data, const common::Time& time) {
        return imu_data.time < time;
      });

17. std::prev 获取一个距离指定迭代器 n 个元素的迭代器,而不改变输入迭代器的值
  // 默认 n 为1,当 n 为正数时, 其返回的迭代器将位于 it 左侧;
  // 反之, 当 n 为负数时, 其返回的迭代器位于 it 右侧

  // 获取 [0, n-1] 范围的预测位姿

  for (auto it = times.begin(); it != std::prev(times.end()); ++it) {
    poses.push_back(ExtrapolatePose(*it).cast<float>());
  }

18. template <typename DataType>
// 函数模板的调用使用 实参推演 来进行
// 类模板 模板形参的类型必须在类名后的尖括号中明确指定, 不能使用实参推演
// 在类外声明一个 函数模板, 使用 实参推演 的方式来使得 类模板可以自动适应不同的数据类型
// 根据传入的data的数据类型,自动推断DataType, 实现一个函数处理不同类型的传感器数据

template <typename DataType>
std::unique_ptr<Dispatchable<DataType>> MakeDispatchable(
    const std::string &sensor_id, const DataType &data) {
  return absl::make_unique<Dispatchable<DataType>>(sensor_id, data);
}

19. auto*(指针类型说明符), auto&(引用类型说明符), auto &&(右值引用)

      // 获取当前队列中时间最老的一个的一个数据

const auto* data = it->second.queue.Peek<Data>();

20. map::emplace() 返回的 pair 对象
  // pair 的成员变量 first 是一个指向插入元素或阻止插入的元素的迭代器
  // 成员变量 second 是个布尔值, 表示是否插入成功, 如果这个元素的索引已经存在插入会失败,返回false

  auto emplace_result = common_start_time_per_trajectory_.emplace(
      trajectory_id, common::Time::min());
  common::Time& common_start_time = emplace_result.first->second;

21. 移动构造函数, 只在使用的时候编译器才会自动生成
  // 这里是显示指定让编译器生成一个默认的移动构造函数

  OrderedMultiQueue(OrderedMultiQueue&& queue) = default;

22. std::shared_ptr 主要的用途就是方便资源的管理, 自动释放没有指针引用的资源
    // 使用引用计数来标识是否有其余指针指向该资源.(注意, shart_ptr本身指针会占1个引用)
    // 引用计数是分配在动态分配的, std::shared_ptr支持拷贝, 新的指针获可以获取前引用计数个数

23. lambda表达式

          [node, handler, trajectory_id, topic](const typename MessageType::ConstPtr& msg) {
            (node->*handler)(trajectory_id, topic, msg);
          }

24. std::forward_as_tuple tuple的完美转发
  // 该 tuple 在以右值为参数时拥有右值引用数据成员, 否则拥有左值引用数据成员
  // c++11: std::piecewise_construct 分次生成tuple的标志常量
  // 在map::emplace()中使用forward_as_tuple时必须要加piecewise_construct,不加就报错
  // https://www.cnblogs.com/guxuanqing/p/11396511.html

  // 以1ms, 以及重力常数10, 作为参数构造PoseExtrapolator

  extrapolators_.emplace(
      std::piecewise_construct,
      std::forward_as_tuple(trajectory_id),
      std::forward_as_tuple(
          ::cartographer::common::FromSeconds(kExtrapolationEstimationTimeSec),
          gravity_time_constant));

25. =delete: 禁止编译器自动生成默认函数; =default: 要求编译器生成一个默认函数

  // 禁止编译器自动生成 默认拷贝构造函数(复制构造函数)
  Node(const Node&) = delete;
  // 禁止编译器自动生成 默认赋值函数
  Node& operator=(const Node&) = delete;

26. std::unordered_map 是采用哈希数据结构实现的, 内部数据保存是无序的
  // 相对于std::map, unordered_map的查找和插入的效率高, 时间复杂度为常数级别O(1),而额外空间复杂度则要高出许多
  // 对于需要高效率查询的情况, 使用unordered_map容器, 但是unordered_map对于迭代器遍历效率并不高

  // These are keyed with 'trajectory_id'.
  std::map<int, ::cartographer::mapping::PoseExtrapolator> extrapolators_;
  std::map<int, ::ros::Time> last_published_tf_stamps_;
  std::unordered_map<int, TrajectorySensorSamplers> sensor_samplers_;
  std::unordered_map<int, std::vector<Subscriber>> subscribers_;

27. std::tie()函数可以将变量连接到一个给定的tuple上,生成一个元素类型全是引用的tuple

  // 根据Lua配置文件中的内容, 为node_options, trajectory_options 赋值

  std::tie(node_options, trajectory_options) =
      LoadOptions(FLAGS_configuration_directory, FLAGS_configuration_basename);

28. std::move 是将对象的状态或者所有权从一个对象转移到另一个对象,
  // 只是转移, 没有内存的搬迁或者内存拷贝所以可以提高利用效率,改善性能..
  // 右值引用是用来支持转移语义的.转移语义可以将资源 ( 堆, 系统对象等 ) 从一个对象转移到另一个对象,
  // 这样能够减少不必要的临时对象的创建、拷贝以及销毁, 能够大幅度提高 C++ 应用程序的性能.
  // 临时对象的维护 ( 创建和销毁 ) 对性能有严重影响.

  // Node类的初始化, 将ROS的topic传入SLAM, 也就是MapBuilder

  Node node(node_options, std::move(map_builder), &tf_buffer,
            FLAGS_collect_metrics);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值