【问题标题】:Who should clear a vector retrieved by reference?谁应该清除通过引用检索到的向量?
【发布时间】:2015-08-30 17:53:51
【问题描述】:

假设我有这个非常简单的代码:

  std::vector<int> getVector( int size )
  {
      std::vector<int> res;
      for ( size_t i = 0; i != size; ++i )
          res.push_back( i* 23 );
      return res;
  }
  
  int main ()
  {
      std::vector<int> v;
      v = getVector(10);
      std::cout << "Size1 is " << v.size() << std::endl;
      v = getVector(15);
      std::cout << "Size2 is " << v.size() << std::endl;
  }

它输出:

Size1 为 10

Size2 为 15

现在,假设我想更改getVector 以避免无用的对象复制并优化速度和内存使用。所以getVector 现在将获得对要填充的向量的引用。

  void getVector( int size, std::vector<int>& res )
  {
      for ( size_t i = 0; i != size; ++i )
          res.push_back( i* 23 );
  }

现在,如果我改变主函数,不注意:

  int main ()
  {
      std::vector<int> v;
      getVector(10,v);
      std::cout << "Size1 is " << v.size() << std::endl;
      getVector(15,v);
      std::cout << "Size2 is " << v.size() << std::endl;
  }

它输出:

Size1 为 10

Size2 为 25

这不是我最初拥有的。

我已经多次进行这种更改(用引用传递的对象替换返回值),而且我一直在问自己同样的问题:谁应该清除向量?

是否有任何“指导方针”或“一般规则”说明在这种情况下谁应该清除向量?

函数本身应该做吗?

  void getVector( int size, std::vector<int>& res )
  {
      res.clear();
      for ( size_t i = 0; i != size; ++i )
          res.push_back( i* 23 );
  }

或者调用者应该这样做?

  int main ()
  {
      std::vector<int> v;
      getVector(10,v);
      std::cout << "Size1 is " << v.size() << std::endl;
      v.clear();
      getVector(15,v);
      std::cout << "Size2 is " << v.size() << std::endl;
  }

编辑:

正如许多人所报告的那样,示例可能是错误的,因为它是“糟糕的优化”。但是可能存在通过引用检索向量并且问题仍然存在的情况。例如:

bool hasData( std::vector<int>& retrievedData );

void splitVector( const std::vector<int>& originalVector,
                  std::vector<int>& part1,
                  std::vector<int>& part2 );

...

【问题讨论】:

  • 没有“唯一正确的方法”。这是在这种特定情况下最有意义的事情。这实际上取决于您传递引用而不是返回值的原因,您只会出于某些不寻常的原因这样做。
  • “现在,假设我想更改 getVector 以避免无用的对象复制并优化速度和内存使用。”。你知道,矢量是可移动的。
  • 如果函数的功能表现为从一个空向量开始,那么函数应该清除它
  • 还请注意,如果您在 C++11 中,由于移动语义,按值返回只是指针的交换。此外,您的案例中还有 RVO 和 NRVO。
  • 您甚至必须询问谁应该清除向量这一事实很好地暗示了当您可以按值返回时接受引用是一种反模式。

标签: c++ vector pass-by-reference


【解决方案1】:

现在,假设我想更改 getVector 以避免无用的对象 复制并优化速度和内存使用。所以 getVector 现在会得到 对要填充的向量的引用。

有些事情你应该考虑:

  • 在您的第一个示例中,由于 RVO/NRVO 编译器优化,因此没有副本。
  • 如果您使用 C++11,所有 STL 容器都有移动构造函数。所以,永远不会在这种情况下被复制。
  • 如果您希望收到参考,除了在呼叫者方面清除之外没有其他(正确)方法。
  • 您可以在getVector 函数的开头使用vector::reserve 稍微优化这个sn-p。

【讨论】:

  • 如果你必须添加你可以通过使用vector::reserve来优化这个sn-p那么你不应该鼓励在同一个列表中按值返回(第二个getVector 会导致不必要的分配!) - 否则很好。
  • @BeyelerStudios 我的编译器 (gcc) 显示每个 getVector 的 5 次重新分配(在第一个示例中)。因此,对于 2 个函数,调用它的 2 个分配 vs 10 个。
  • 感谢您的帖子,我不确定这些内存编译器优化是否真的适用于这种情况……很高兴知道它们确实如此。但主要问题(“什么是公会线”)仍然存在于其他情况下:例如,一个函数填充(然后“返回”)许多不同的向量......你仍然需要确定谁清除它们......跨度>
  • @PSIAlt 对于具有保留内存的向量和使用引用的函数只需要一次分配
  • @BeyelerStudios 是的,但我不想这样做,尤其是在一个大项目中。
【解决方案2】:

在您的第一个示例中,该函数称为“getVector”,它适合描述该函数的作用。正如预期的那样,它返回一个包含一定数量元素的向量。

在第二个例子中同样不适用。该函数仍称为“getVector”,但现在您要清除向量。它不再描述该函数,因为您将向量传递给它并用它做事。它更像是“takeAVectorClearItAndInitializeIt”。而且您可能不想命名这样的函数或在代码中使用它。 (当然我有点夸大其词,但你懂的)

为了让一个函数易于理解,它应该有一个清晰的函数,而不是在后台做隐藏的事情(比如清除客户端的向量)。如果我要使用你的函数,我可能会对为什么我的向量刚刚被清除感到困惑。

通常是客户端代码负责管理自己的变量。即使客户端代码调用一个函数来清除向量,责任仍然在客户端代码中以清除其向量的意图。在您的情况下,您从客户端代码中知道“getVector”函数的作用,因为您将它们都制作了所以它不会让您感到困惑,但是当您编写只需要两个函数之一(清除或初始化)的代码时,您可能做一些改变会影响使用这个函数的所有代码并产生更多问题。


关于更具体地回答您的问题。

如果您不想复制传递引用的向量,但可以将函数命名为更具描述性的名称,例如“initializeVector”,以便更清楚您在做什么。

您通常应该从客户端代码中调用“clear”,因此在遇到性能问题之前,您的第一个示例会更好。 (清晰是隐含的,因为你得到了一个不同的向量)

【讨论】:

    【解决方案3】:

    没有单一的权利。但想想 stringstream 类对象以供参考。打算重用该类对象的人需要在重用该对象之前进行清理。创建对象的人通常负责删除相同的 .

    【讨论】:

    • 这是回答这个问题的好方法......看看在 STL 中做了什么作为“指导方针”。
    • @jpo38 我担心你过度优化你的代码。不要那样做:代码会变得更糟,即:不可读、不可维护、……很快。
    • @Wolf:对于这个特定的例子,你是对的。但这是一个普遍的问题(这个例子可能是错误的)。您可能会遇到这样的情况:函数通过引用将一堆变量作为参数(设置/修改它们),而有些可能是向量......
    • @jpo38 然后,探索是否可以重新定制对象以获得简单的生命周期规则。对于const 引用参数没有问题,但如果它们是非常量,就会弹出很多问题。您的实际问题是在哪里放置 clear - 这显然是不可概括的。
    【解决方案4】:

    都不是。相反,您应该重新考虑您的设计决策。您为消除(可能是理论上的)性能问题而执行的操作 - 我从您的措辞中读到了这一点

    避免无用的对象复制并优化速度和内存使用

    替换两个具体问题:

    1. 这个函数的性能瓶颈是什么?
    2. 出于性能原因,我是否应该避免使用std::vector

    相对容易回答,抽象而难回答,没有真正的答案,但它会给您(和我们)带来严重的头痛:您的“优化”实际上会导致性能下降 .

    【讨论】:

      猜你喜欢
      • 2017-11-27
      • 1970-01-01
      • 2014-08-05
      • 1970-01-01
      • 2015-01-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多