【问题标题】:compact vector a small memory foot print vector紧凑向量 一个小的内存 脚印向量
【发布时间】:2012-04-03 20:50:06
【问题描述】:

我维护一个旧的 C++ 应用程序,它有很多类,如下所示:

    class ClassWithALotOfVectors
    {
        std::vector<int> _vector1;
        std::vector<int> _vector2;

        // a lot of other vector datamembers go here

        std::vector<double> _vectorN;
    };

即 - 一种数据类型,其中成员为 double 或 int 向量。 问题是 - 这些向量永远不会同时填充 - 因此 当我们创建 100000 个 ClassWithALotOfVectors 实例时 - 内存使用量增加 尽管这些向量中只有 10% 正在使用中,但这个数字令人印象深刻。

所以我决定写一个小的“按需分配”向量类。

它是 std::vector 的包装器 - 内部向量仅在何时创建 第一次访问(使用两个 getter - ref() 和 const_ref() 方法)

当我用 compact_vector 替换 ClassWithALotOfVectors 类中的 std::vector 时 如下:

    class ClassWithALotOfVectors2
    {
    compact_vector<int> _vector1;
    compact_vector<int> _vector2;

    compact_vector<double> _vectorN;
    };

做了一些测试,结果很有希望 - 内存使用量下降了 戏剧性地,但突然发现应用程序没有释放内存 最后 - 内存消耗的增长速度比它慢得多 曾经是 - 但应用程序似乎没有释放内存 最后。

你能看看我对 compact_vector 的实现吗 看看你是否能发现内存管理有问题。

    template <class T> class compact_vector 
    { 
    public: 
            compact_vector<T>()
            :_data(NULL) 
            { 
            } 

            ~compact_vector<T>() 
            { 
                    clear(); 
            } 

            compact_vector<T>(const compact_vector<T> & rhs) 
            :_data(NULL) 
            { 
                    if (NULL != rhs._data) 
                    { 
                            _data = new std::vector<T>(*(rhs._data)); 
                    } 
                    else 
                    { 
                            clear(); 
                    } 
            } 

            //      assignment 
            // 
            compact_vector<T> & operator=(const compact_vector<T> & rhs) 
            { 
                    if (this != &rhs) 
                    { 
                            clear(); 
                            if (NULL != rhs._data) 
                            { 
                                    _data = new std::vector<T>(*rhs._data); 
                            } 
                    } 
                    return *this; 
            } 
            compact_vector<T> & operator=(const std::vector<T> & rhs) 
            { 
                    clear(); 
                    if (!rhs.empty()) 
                    { 
                            _data = new std::vector<T>(rhs); 
                    } 
                    return *this; 
            } 

            const std::vector<T> & const_ref() 
            { 
                    createInternal(); 
                    return *_data; 
            } 
            std::vector<T> & ref() 
            { 
                    createInternal(); 
                    return *_data; 
            } 
            void    clear() 
            { 
                    if (NULL != _data) 
                    { 
                            _data->clear(); 
                            delete _data; 
                            _data = NULL; 
                    } 
            } 
    private: 
            void    createInternal() 
            { 
                    if (NULL == _data) 
                    { 
                            _data = new std::vector<T>(); 
                    } 
            } 

    private: 
            compact_vector<T>(const std::vector<T> & rhs) 
            { 
            } 
            compact_vector<T>(std::vector<T> & rhs) 
            { 
            } 
            compact_vector<T>(std::vector<T> rhs) 
            { 
            } 

            std::vector<T> * _data; 
    }; 

【问题讨论】:

  • 您的clear 方法可以大大简化为delete _data; _data = 0;
  • 如果可能的话,你现在应该使用nullptr,尽管NULL 仍然可以。
  • stack exchange有一个code review部分
  • @Peter 正如他们的常见问题解答所述,Code Review 用于工作代码。这个问题实际上是关于代码中的一个问题:存在内存泄漏。我觉得这里比较合适。
  • @R.MartinhoFernandes 是的,我现在明白了。我并不清楚快速浏览是否有问题。也许编辑问题以更清楚地表明正在寻求什么帮助。

标签: c++ memory-management stl vector


【解决方案1】:

std::vector 的大多数实现在您需要之前不会获取内存,并且向量的大小通常只是几个(3 + 可能是额外的调试信息)指针。也就是说,std::vector&lt;int&gt;() 不会为任何对象分配空间。我相信你在这里对着错误的树吠叫。

为什么您的原始代码的内存使用量要高得多?你期待clear() 释放内存吗?

如果是这样,那你就错了。 clear() 函数会破坏包含的元素,但不会释放分配的内存。考虑添加一个包装器来清除使用以下习语的内存:

std::vector<int>().swap( _data );

上一行所做的是创建一个新的 empty 向量(通常不附加内存,不是强制的,但常见的实现)并交换两个向量的内容。在表达式的末尾,临时包含最初由_data 向量保存的数据,而_data 为空且没有内存(已定义实现)。临时在完整表达式结束时被破坏并释放内存。

或者,如果您的编译器支持 C++11,您可以在调用 clear() 后使用 shrink_to_fit()。该标准不要求 shrink_to_fit() 实际上缩小以适应,但对于空向量,我希望实现能够做到这一点。调用后调用capacity() 进行测试,看看它是否已降至 0。

【讨论】:

  • 即使 clear() 不会释放内存,下一行 delete 会做,不是吗?
  • @Andrew:如果不清楚:我建议通过消除对整个类型的需求来消除compact_vector 中的错误。如果仅仅因为 empty 向量而导致您的内存占用很高,那么您可能需要真正考虑设计。
  • 原始代码的内存使用率很高,因为每个 ClassWithALotOfVectors 类有 50 个向量,而这 50 个向量中只有 10 个被实际使用。你说空的 std::vector 只有 3-5 个指针的内存开销,而我的 compact_vector 中的内存开销只有一个指针(_data),这使得它在大多数情况下内存效率提高了 3-4 倍我的用例(正如我所说,50 个向量中只有 10 个是非空的)
  • 功能齐全的std::vector 的最小可能大小是一个指针大小和两个整数大小(32 位上的 12 个字节)。不过,大多数实现倾向于使用 3 个指针,因为大小相同,并且通常这样更容易/更清晰。不过,32 位上的 12 字节。然后你忽略了一些重要的事情......实际上让你的_data 在堆上的开销。任何对堆的访问都是昂贵的,最好避免。在堆栈上添加一些额外的字节(或者那些 compact_vectors 预计会在堆上?我希望不会)。
  • @David - 我在这里看到了你关于设计的观点,我同意,如果它不是一个 old 应用程序,我宁愿不做太多改变,因此我打算compact_vector 旨在为内存问题提供简单/非侵入式的修复。
【解决方案2】:

Use smart pointersIn this case, std::unique_ptr 带有手工复制 ctor / 赋值运算符。突然间,你所有的问题都解决了。就像魔术一样!

很抱歉听起来很刺耳,但内存泄漏总是有相同的答案:智能指针。

【讨论】:

  • 这不会将内存消耗增加到原始向量的水平吗?
  • @BoPersson unique_ptr 的任何合理实现都与指针具有相同的占用空间。
  • @Xeo 不幸的是,我不能使用 std::unique_ptr,因为它仅在最新版本的 C++ 中可用,而我的应用程序必须在 Visual Studio 2005 上编译,距离发布还有 5 年C++0x 兼容。更重要的是 - 如果有的话,我真的看不出我的 compact_vector 泄漏内存的位置......
  • @Andrew:Boost 有unique_ptrscope_ptr。如果您的对象不需要可复制或在容器中使用,那么您也可以使用good old std::auto_ptr.
  • @DavidRodríguez-dribeas 不确定 Andrew 的情况,但许多公司坚持使用他们已经使用的库。
【解决方案3】:

既然你说它是一个旧应用程序,不能用它做很多事情。在构造过程中保留所有大小为零的向量怎么样。
这很乏味,但是由于旧应用程序并且预计不会进行太多修改。可能值得一试。

class ClassWithALotOfVectors    
{    
   std::vector<int> _vector1;     
   std::vector<int> _vector2;        
   // a lot of other vector datamembers go here         
   std::vector<double> _vectorN;

  public:
     ClassWithALotOfVectors() : Init() { }   
     void Init()   
     {   
         vector1.reserve(0);   
         /// Like wise other vectors.   
     }  
};

【讨论】:

  • 谢谢,但 'reserve(0)' 不是默认的 'vector' 构造函数默认做的吗?
【解决方案4】:

一次只使用一个向量的想法听起来像union 概念。果然,C++ 有一个名为anonymous union 的元素正是这样做的。

class ClassWithALotOfVectors {
    public:
    union  {
        std::vector<double> _vector1;
        std::vector<double> _vector2;
    };
    ClassWithALotOfVectors() { new(&_vector1) std::vector<double>; };
    ~ClassWithALotOfVectors() { _vector1.~vector<double>(); };
};

因为联合不知道要调用哪个元素的构造函数,所以联合中禁用了默认构造函数,您必须手动构造和解构联合的元素,但我认为这将完成您正在寻找的,别名@987654325 @ 和 _vector2 到同一个元素,但都放在 ClassWithALotOfVectors 命名空间中,然后在调用 ClassWithALotOfVectors 的析构函数时释放向量。

有关匿名工会的更多信息,请参阅: CPP reference: Anonymous Unions

【讨论】:

    猜你喜欢
    • 2020-11-05
    • 2021-02-03
    • 2017-03-28
    • 1970-01-01
    • 2012-10-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多