【问题标题】:c++ : alternative for Vector of references to avoid copying large datac++ : Vector of references 的替代方案,以避免复制大数据
【发布时间】:2013-01-24 13:23:45
【问题描述】:

我花了一些时间寻找答案,但没有找到任何令人满意的答案。

只是对一些更有经验的 C++ 人如何解决这类问题感兴趣,因为现在我正在做的与生产相关的编码比原型设计要多。

假设你有一个类,它有一个 unordered_map (hashmap),它包含大量数据,比如 500Mb。您想编写一个访问器,以有效的方式返回该数据的某个子集。

采取以下方法,其中 BigData 是一些存储适量数据的类。

Class A
{
   private:
      unordered_map<string, BigData> m_map;   // lots of data

   public:

    vector<BigData>   get10BestItems()
    {
        vector<BigData>  results;
        for ( ........  // iterate over m_map and add 10 best items to results
        // ... 
       return results;
    }

};

访问器 get10BestItems 在此代码中效率不高,因为它首先将项目复制到结果向量,然后在函数返回时复制结果向量(从函数堆栈复制)。

由于各种原因,您不能在 c__ 中拥有引用向量,这将是显而易见的答案:

vector<BigData&> results;     // vector can't contain references.

您可以在堆上创建结果向量并传递对它的引用:

vector<BigData>&   get10BestItems()    // returns a reference to the vector
    {
        vector<BigData>  results = new vector<BigData>;   // generate on heap
        for ( ........  // iterate over m_map and add 10 best items to results
            // ... 
       return results;   // can return the reference 
    } 

但是,如果您不小心,您将遇到内存泄漏问题。它也很慢(堆内存)并且仍然将数据从地图复制到向量。

所以我们可以回顾一下 c 风格的编码,只使用指针:

vector<BigData*>   get10BestItems()    // returns a vector of pointers
    {
        vector<BigData*>  results ; // vectors of pointers
        for ( ........  // iterate over m_map and add 10 best items to results
        // ... 
       return results;  
    } 

但大多数消息来源说除非绝对必要,否则不要使用指针。有使用 smart_pointers 和 boost ptr_vector 的选项,但我宁愿尽量避免这些。

我不认为地图将是静态的,所以我不太担心错误的指针。如果代码必须不同来处理指针,这只是一个问题。从风格上看,这并不令人愉快:

const BigData&   getTheBestItem()    // returns a const reference
{
       string bestID;
       for ( ........  // iterate over m_map, find bestID
       // ... 
       return m_map[bestID] ; // return a referencr to the best item
}


vector<BigData*>   get10BestItems()    // returns a vector of pointers
{    
        vector<BigData*>  results ; // vectors of pointers
        for_each ........  // iterate over m_map and add 10 best items to results
        // ... 
       return results;  
 };

例如,如果您想要单个项目,则返回引用很容易。

最后的选择是简单地将 Hash-map 公开并返回一个键向量(在本例中为字符串):

Class A
{
      public:

         unordered_map<string, BigData> m_map;   // lots of data



    vector<string>   get10BestItemKeys()
    {
        vector<string>  results;
        for (........  // iterate over m_map and add 10 best KEYS to results
        // ... 
       return results;
    }

};



A aTest;
... // load data to map

vector <string> best10 =  aTest.get10BestItemKeys();
for ( .... // iterate over all KEYs in best10
{
    aTest.m_map.find(KEY);  // do something with item.
    // ...
} 

什么是最好的解决方案?速度很重要,但我想要易于开发和安全的编程实践。

【问题讨论】:

  • “避免复制大数据” - 那么可能是对引用向量的引用
  • 这与您的问题没有直接关系,但正如您所说get10BestItems() 效率不高,我相信您可以将时间复杂度降低到 $O(K) + O(N \ log K)$ 和空间复杂度为 $O(K)$,其中 $N$ 是元素的数量,$K = 10$。您可以使用堆来保留 10 个最佳值,并使用比较函数在两个不同的值之间进行比较。你可以在这里看到我的意思的实现:github.com/lakshayg/collections/blob/master/algos/ksmallest.cpp

标签: c++ pointers vector reference accessor


【解决方案1】:

我会做类似以下的事情:

Class A
{
private:
    unordered_map<string, BigData> m_map;   // lots of data
    vector<BigData*> best10;

public:
    A()
        : best10(10)
    {
        // Other constructor stuff
    }

    const vector<BigData*>&   get10BestItems()
    {
       // Set best10[0] through best10[9] with the pointers to the best 10
       return best10;
    }

};

注意几点:

  • 向量不是每次都重新分配,而是作为常量引用返回,因此当您调用 get10BestItems 时不会分配或复制任何内容。

  • 在这种情况下指针就可以了。您阅读的有关避免指针的内容可能与堆分配有关,在这种情况下,std::unique_ptrstd::shared_ptr 现在是首选。

【讨论】:

  • 感谢您的快速回复。似乎是一种共识,在这种情况下指针并不太邪恶。
【解决方案2】:

如果地图是恒定的,我只会使用指针向量。如果你想避免数据被改变,你总是可以返回 const 指针。

参考在工作时非常有用,但我们仍然有指针是有原因的(对我来说,这属于“必要”的范畴)。

【讨论】:

    【解决方案3】:

    对我来说,这听起来像是 boost::ref 的工作。只需稍微更改您的原始代码:

    typedef std::vector<boost::ref<BigData> > BestItems;
    
    BestItems  get10BestItems()
        {
            BestItems  results;
            for ( ........  // iterate over m_map and add 10 best items to results
            // ... 
           return results;
        }
    

    现在,您实际上只是在返回向量中返回对每个项目的引用,使其复制起来既小又便宜(如果编译器无法完全优化返回副本)。

    【讨论】:

    • 感谢您的快速回复。我将研究 boost:ref,它是 c++11 的一部分吗?
    【解决方案4】:

    我通常使用boost::range,我发现它在很多情况下都非常有用,尤其是你描述的那种。

    您可以保留范围对象并对其进行迭代等。

    但我应该提一下,如果您在获取范围和使用它之间添加/删除对象,我不知道会发生什么,因此您可能需要在使用它之前检查一下。

    【讨论】:

      猜你喜欢
      • 2011-08-31
      • 2021-12-14
      • 1970-01-01
      • 1970-01-01
      • 2019-09-11
      • 1970-01-01
      • 2011-02-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多