【问题标题】:Best way to return list of objects in C++?在 C++ 中返回对象列表的最佳方法?
【发布时间】:2009-02-05 07:24:58
【问题描述】:

我已经有一段时间没有用 C++ 编程了,从 python 出来后,我觉得穿着直筒夹克很不错,好吧我不吐槽了。

我有几个函数充当“管道”,接受一个列表作为输入,返回另一个列表作为输出(基于输入),

这是概念上的,但实际上,我使用std::vector 来表示列表,这可以接受吗?

此外,我没有使用任何指针,所以我使用std::vector<SomeType> the_list(some_size);作为变量,并直接返回它,即return the_list;

附:到目前为止一切正常,项目规模很小,这似乎不会影响性能,但我仍然想就此获得一些输入/建议,因为我觉得我正在用 C++ 编写 python。

【问题讨论】:

  • 您如何访问列表中的数据?如果您不需要随机访问和连续内存,并且您需要大量插入删除数据,那么链表可能会更好。
  • 注意链表(例如 std::list)每个元素的内存开销更高,并且通常仅在最罕见的用例中胜过向量。
  • 在我目前的特殊情况下,我实际上需要任意访问......但我想让这个问题很笼统,所以链表听起来是个好建议
  • 现在从 C# 返回。很多很多“直筒夹克”+1! :D

标签: c++ arrays memory-management


【解决方案1】:

我唯一能看到的是您强制返回您返回的列表的副本。这样做会更有效:

  void DoSomething(const std::vector<SomeType>& in, std::vector<SomeType>& out)
  {
  ...
  // no need to return anything, just modify out
  }

因为你传入了你想要返回的列表,所以你避免了额外的副本。

编辑:这是一个旧回复。如果您可以使用具有移动语义的现代 C++ 编译器,则无需担心这一点。当然,如果您返回的对象没有移动语义,这个答案仍然适用。

【讨论】:

  • 这是正确的,复制一个向量可能很糟糕,但前提是你有大量的向量并且你复制了很多。
  • 什么是海量向量?我们不知道 SomeType 有多大或向量有多长。使用 refs 和 const refs 是一种很好的做法。
  • 实际上,如果不是全部的话,大多数半体面的 C++ 编译器都支持 RVO(返回值优化),它应该通过简单地转换代码以传递对返回容器的引用来处理“不必要的”副本.这是几乎每个编译器中都存在的为数不多的优化之一。
  • c++1x 会将返回值移动到它们的目的地。返回一个向量然后就相当于理想情况下的指针交换操作。对于c ++ 1x,我将按值获取参数,并让它们从临时对象中移动构造(使用它们的移动构造函数),修改它们,并将它们向前移动
  • @Timo Geusch:不过,你会相信它吗?
【解决方案2】:

如果您真的需要一个新列表,我会简单地返回它。在大多数情况下,返回值优化不会处理不必要的副本,并且您的代码保持非常清晰。
话虽如此,获取列表并返回其他列表确实是 C++ 中的 Python 编程。

A,对于 C++,更合适的范例是创建采用一系列迭代器并改变底层集合的函数。

例如

void DoSomething(iterator const & from, iterator const & to);

(迭代器可能是一个模板,取决于您的需要)

链式操作就是在 begin() 和 end() 上调用连续方法的问题。 如果您不想更改输入,请先自己制作副本。

std::vector theOutput(inputVector);

这一切都源于 C++ “不要为不需要的东西付费”的理念,您只需在您真正想要保留原件的地方创建副本。

【讨论】:

  • 问题是,我在函数内部创建了输出列表,它相对于输入列表的大小有点随意
  • 没有理由不能使用迭代器插入/删除元素。但是,如果您在概念上要创建完全不同的东西,为什么不使用例如第三个 std::back_inserter 迭代器或类似的东西?
  • 同意。例如,函数模板 std::remove_copy 使用这种 3-arg 方法来“创建”一个大小有些随意的输出。它是依赖注入:与其创建 std::vector (或其他),不如由调用者提供一个对象来接收输出。
【解决方案3】:

我会使用通用方法:

template <typename InIt, typename OutIt>
void DoMagic(InIt first, InIt last, OutIt out)
{
  for(; first != last; ++first) {
    if(IsCorrectIngredient(*first)) {
      *out = DoMoreMagic(*first);
      ++out;
    }
  }
}

现在你可以调用它了

std::vector<MagicIngredients> ingredients;
std::vector<MagicResults> result;

DoMagic(ingredients.begin(), ingredients.end(), std::back_inserter(results));

您可以在不更改使用的算法的情况下轻松更改使用的容器,而且返回容器没有开销,效率很高。

【讨论】:

  • 是的。这绝对是这样做的方法,特别是如果你正在“管道”事情。
【解决方案4】:

如果你想成为真正的铁杆,你可以使用boost::tuple

tuple<int, int, double> add_multiply_divide(int a, int b) {
  return make_tuple(a+b, a*b, double(a)/double(b));
}

但是,由于您的所有对象似乎都是单一的非多态类型,因此 std::vector 一切都很好。 如果您的类型是多态的(基类的继承类),那么您需要一个指针向量,并且您需要记住在丢弃您的向量之前删除所有分配的对象。

【讨论】:

    【解决方案5】:

    在许多情况下,使用 std::vector 是最好的方法。它保证使用连续的内存,因此对 L1 缓存很满意。

    你应该知道当你的返回类型是 std::vector 时会发生什么。幕后发生的事情是 std::vector 是递归复制的,所以如果 SomeType 的复制构造函数很昂贵,“return 语句”可能是一个冗长且耗时的操作。

    如果您在列表中搜索和插入很多内容,您可以查看 std::set 以获得对数时间复杂度而不是线性时间复杂度。 (std::vectors insert 在超过其容量之前是恒定的)。

    你是说你有很多“管道函数”......听起来像是 std::transform 的绝佳场景。

    【讨论】:

    • vector 实际上做了一个惰性复制,并且只是共享vector的内容,如果你真的想复制它,你必须显式调用std::copy。所以传递一个向量作为回报并不比正常重。
    • 你确定vector做了一个惰性复制吗?我没有意识到这一点,并且有点谷歌搜索似乎表明它没有。虽然我可能完全错了。我不经常使用 STL。
    • 嗯,我不是 100% 确定,因为我认为它的实现是基于,但是如果你从堆栈上的一个向量分配一个向量,然后释放该向量,堆栈向量就会被吹走.
    • 不,你只是在编造,罗伯特
    • Robert,JTC1/SC22/WG21 - C++ 标准委员会还有其他说法。在对特定功能的行为做出假设之前,请阅读标准。
    【解决方案6】:

    返回对象列表的另一个问题(与处理一个或两个列表相反,正如 BigSandwich 指出的那样)是,如果您的对象具有复杂的复制构造函数,则这些构造函数将调用容器中的每个元素。

    如果您有 1000 个对象,每个对象都引用一块内存,并且它们将该内存复制到对象 a、b 上; a=b;这对你来说是 1000 个内存副本,只是为了将它们包含在一个容器中返回。如果你还想直接返回一个容器,考虑一下这种情况下的指针。

    【讨论】:

      【解决方案7】:

      它的工作原理非常简单。

      list<int> foo(void)
      {
          list<int> l; 
          // do something
          return l;
      }
      

      现在接收数据:

      list<int> lst=foo();
      

      是完全最优的,因为编译器知道优化 lst 的构造函数。和 不会导致复制。

      其他方法,更便携:

      list<int> lst;
      // do anything you want with list
      lst.swap(foo());
      

      会发生什么: foo 已经优化,因此返回值没有问题。什么时候 您调用 swap 您将 lst 的值设置为 new,因此不要复制它。现在的旧值 的 lst 被“交换”并销毁。

      这是完成这项工作的有效方式。

      【讨论】:

      • Visual Studio 2015 下划线红色波浪线列表,并说“标识符列表未定义”
      猜你喜欢
      • 1970-01-01
      • 2010-11-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-06-16
      • 1970-01-01
      相关资源
      最近更新 更多