C++ 谁应该清除通过引用检索的向量?

C++ 谁应该清除通过引用检索的向量?,c++,vector,pass-by-reference,C++,Vector,Pass By Reference,假设我有一个非常简单的代码: 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

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

  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;
  }
现在,如果我在不注意的情况下更改主功能:

  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;
  }
int main()
{
std::向量v;
getVector(10,v);

std::cout没有单一的权利。但是考虑stringstream类对象作为参考。打算重用该类对象的人需要在重用该对象之前进行清理。创建该对象的人通常负责删除该对象

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

  void getVector( int size, std::vector<int>& res )
  {
      for ( size_t i = 0; i != size; ++i )
          res.push_back( i* 23 );
  }
有些事情你应该考虑一下:

  • 在第一个示例中,没有复制,因为RVO/NRVO编译器优化
  • 如果您使用C++11,所有STL容器都有移动构造函数。因此,在这种情况下,将永远不会复制
  • 如果您更愿意接收引用,那么除了呼叫方的清除之外,没有其他(正确的)方法
  • 您可以使用
    getVector
    函数开头的
    vector::reserve
    稍微优化此代码段

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

在第二个示例中,同样的情况不适用。该函数仍然称为“getVector”,但现在您希望清除该向量。它不再描述该函数,因为您正在向它传递一个向量并使用它进行操作。它更像是“TakeAvectorClearItadInitializeIt”。您可能不想命名这样的函数,也不想在代码中使用它。(当然,我有点夸张,但您明白了)

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

通常是客户机代码负责管理自己的变量。即使客户机代码调用函数来清除向量,也要在客户机代码中负责清除向量。在您的情况下,您可以从客户机代码中知道“getVector”是什么函数会这样做,因为这两个函数都是您创建的,所以不会让您感到困惑,但当您编写只需要两个函数中的一个(清除或初始化)的代码时,您可能会执行一些更改,这些更改会影响使用此函数的所有代码,并产生更多问题


让我们更具体地回答你的问题

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


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

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

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

替换两个具体问题:

  • 此功能的性能瓶颈是什么
  • 出于性能原因,我是否应该避免使用
    std::vector

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

    没有“一条正确的道路”。这是在此特定上下文中最有意义的内容。这实际上取决于您传入引用的原因,而不是返回值,您只会出于某些不寻常的原因返回值。“现在,假设我想更改getVector,以避免无用的对象副本,并优化速度和内存使用。”。向量是可移动的,你知道。如果函数的功能if表现为从空向量开始,那么函数应该清除它。另外,请注意,如果在C++11中,由于移动语义,按值返回只是指针的交换。此外,在你的情况下还有RVO和NRVO。事实上,你甚至必须问谁应该d清除向量是一个很好的提示,当您可以按值返回时,接受引用是一种反模式。如果必须添加,您可以使用
    vector::reserve
    稍微优化此代码段,那么您不应该鼓励在同一列表中按值返回(第二个
    getVector
    将导致不必要的分配!)-其他方面的优点。@BeyelerStudios我的编译器(gcc)为每个
    getVector
    (在第一个示例中)显示了5次重新分配。因此,对于2个函数,它调用了2次分配,而不是10次分配。感谢您的帖子,我不确定这些内存编译器优化是否真的应用于本例中……很高兴知道它们确实应用了。但主要的问题是(“什么是基准线”)在其他情况下仍然存在:例如,函数填充(然后“返回”)许多不同的向量…您仍然需要确定谁清除它们…@p对于具有保留内存的向量和使用reference@BeyelerStudios是的,但我还是不想这么做,尤其是在一个大项目中。这是一个很好的回答问题的方法……看看STL做了什么作为“指导线”。@jpo38我担心你过度优化了你的代码。不要这样做:代码会变得更糟,即:不可读,不可维护,……很快。@Wolf:对于这个特定的例子,你是对的。但这是一个普遍的问题(这个例子可能是错误的)。您可能会遇到这样的情况:函数通过引用(设置/修改)将一组变量作为参数,而某些变量可能是向量
      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 );