使用大对象时的STL效率

STL efficiency when using big objects

本文关键字:STL 效率 对象      更新时间:2023-10-16

更具体地说,让我们将问题的范围限制在libstdc++和Visual c++。

考虑存储在容器中的对象具有以下属性的情况:

  • 复制和赋值可能很昂贵
  • 移动和交换是便宜的,永远不会抛出
  • 默认构造函数是便宜的,从不抛出

当添加或删除元素时,一些容器可能会重新分配/移动存储的对象。在这种情况下,上面提到的STL实现在重新分配/移动元素时避免复制吗?

那么std::sort和其他算法呢?

如果你仔细想想,当移动和交换可用时,就不需要复制了。

你可能知道所有STL操作都提供大O复杂性保证。Big O表示有一个常数乘以N的某个函数。我的问题可以改写成这个常数包括什么?它是否包括复制的成本,还是与移动/交换的成本成比例?

谢谢你。

唯一可以给出的一般答案是c++是由非常关心性能的聪明人开发的,所以你通常不会发现容易的优化错过了,也不应该太担心你的标准库的性能。

您可以通过阅读标准中的规范、cppreference.com等网站或随您的实现附带的文档来逐一回答这些问题。例如,如果std::vector::push_back必须重新分配其内部缓冲区,它将使用move构造函数来"复制"元素,当且仅当该构造函数存在并且声明为noexcept(参见std::move_if_noexcept)。

另一种解释在标准库中实际发生的情况的方法是进行测试驱动。测试一个简单的struct来打印来自其构造函数和赋值操作符的日志消息,然后将该类的实例放入标准库容器中,并在其上执行一些算法。下面以std::vectorstd::sort为例。您可以通过使用不同的容器和算法来进行操作。也看看如果你做了注释所指示的更改会发生什么。
#include <algorithm>
#include <iomanip>
#include <iostream>
#include <random>
#include <vector>

struct Example
{
  int id;
  Example(const int id) : id {id}
  {
    std::cout << __PRETTY_FUNCTION__ << std::endl;
  }
  Example(const Example& rhs) : id {rhs.id}
  {
    std::cout << __PRETTY_FUNCTION__ << std::endl;
  }
  // try commenting out the 'noexcept'
  Example(Example&& rhs) noexcept : id {rhs.id}
  {
    std::cout << __PRETTY_FUNCTION__ << std::endl;
  }
  Example&
  operator=(const Example& rhs)
  {
    this->id = rhs.id;
    std::cout << __PRETTY_FUNCTION__ << std::endl;
    return *this;
  }
  // try commenting out the 'noexcept'
  Example&
  operator=(Example&& rhs) noexcept
  {
    this->id = rhs.id;
    std::cout << __PRETTY_FUNCTION__ << std::endl;
    return *this;
  }
  ~Example() noexcept
  {
    std::cout << __PRETTY_FUNCTION__ << std::endl;
  }
};

int
main()
{
  const auto n = 10;
  auto rndeng = std::default_random_engine {};
  auto rnddst = std::uniform_int_distribution<int> {};
  auto elements = std::vector<Example> {};
  std::cout << "CONSTRUCTING VECTOR OF " << n << " ELEMENTS...nn";
  elements.reserve(n);  // try commenting this out
  for (auto i = 0; i < n; ++i)
    elements.emplace_back(rnddst(rndeng));  // try using push_back instead
  const auto cmp = [](const Example& lhs, const Example& rhs){
    return lhs.id < rhs.id;
  };
  std::cout << "nSORTING ELEMENTS...nn";
  std::sort(elements.begin(), elements.end(), cmp);
  std::cout << "nSORTED ELEMENTS:nn";
  for (const auto& elem : elements)
    std::cout << std::setw(16) << elem.id << "n";
  std::cout << "nLEAVING MAIN...nn";
}