【问题标题】:Why does reassigning unique_ptr increases memory usage?为什么重新分配 unique_ptr 会增加内存使用量?
【发布时间】:2014-09-21 03:38:06
【问题描述】:

我有一个基类,以及它的一些多态。我希望能够创建一个类基类型的对象,然后将其变形为派生类,然后返回基类。可以做到这一点吗?我这样做如下,但我不确定这是正确的方法:

假设我有这些课程:

class base {
public:
    int var;
    base();
    virtual ~base();
    virtual void func1();
    void func2();
}

class derived1 : base {
public:
    unique_ptr<otherClass> otherVar;
    vector<unique_ptr<anotherClass>> myVec;
    derived1();
    ~ derived1() {
    otherVar.reset();
    for (unsigned int i=0; i< myVec.size(); i++) {
    myVec[i].reset();
    }
    myVec.clear();
    vector<unique_ptr< anotherClass >>().swap(myVec);
    }
    void  func1(){
       //do something
    }
}

class derived2 : base {
public:
    derived2();
    ~ derived2();
    void  func1(){
       //do something else
    }
}

现在我在代码的其他地方这样做:

  unique_ptr<base> myObject;
  myObject = unique_ptr< derived1 > (new derived1());

如果以后我这样做:

  myObject.reset();
  myObject = unique_ptr< base > (new base());

堆内存显着增加。原因是什么,如何避免这个问题?

编辑1

我在derived1 中添加了更多细节。 我没有收到任何错误,程序运行没有问题,只有当 myObject 变形时内存才会增加。 顺便说一句,myObject 永远不会超出范围,它只会改变它的形状(所以我实际上不确定我使用的是正确的指针类型)。

编辑2

我设法摆脱了类中的大部分指针(不幸的是,我不能在所有情况下都使用普通变量),现在我的内存增长很少。 但我的理解是,使用智能指针应该让生活更轻松,而不是更难。我们可以使用 C++ 中的任何类型的指针,释放内存而不删除它(我认为 reset() 应该这样做),然后稍后重新分配一些其他值?

我必须说,对于简单的对象,这可以通过 unique_ptr 来完成,但对于复杂的类(将指向其他类的指针作为其成员变量),似乎某些内存即使在调用 reset( )。

编辑3

我发现问题不在于代码的 C++ 部分,而在于 OpenGL 部分。抱歉让这里的专家感到困惑 XD。

当我这样做时:

    otherVar.reset();

this otherVar 包含 OpenGL 纹理。这个对象的析构函数应该通过这个调用一次删除所有纹理(至少在 Mac 上):

glDeleteTextures(textureCount, textures);

但在 iOS (OpenGL ES) 上,我必须单独删除纹理,如下所示:

glDeleteTextures(1, &tex.textureID);

不过,我不知道为什么 glDeleteTextures(textureCount, textures) 不会在 iOS 应用程序中一次删除所有纹理,但至少我找到了一种解决方法。

谢谢大家。

【问题讨论】:

  • 您分配了一个新对象并想知道为什么您的计算机要为它分配内存?
  • Now somewhere else in code I do this: If later I do this: 所以我想这意味着你不能用简单的 3 或 4 行程序复制这个问题。你说“其他地方”和“稍后我做”,这表明你的程序比你向我们展示的要大得多。也许您没有向我们展示的正是这种行为。
  • 重新分配后内存变大了。我预计 reset() 会释放一些内存,但它似乎没有任何效果。
  • 你如何知道你的堆大小显着增加以及你如何定义显着?
  • @DARKMATTER 你如何确定内存没有被释放?希望不是通过使用诸如任务管理器或类似程序之类的操作系统工具。

标签: c++ polymorphism unique-ptr


【解决方案1】:

无法确定,但我怀疑您的代码中可能存在循环所有权模式。这是一个简单示例中的样子:

#include <iostream>
#include <memory>

struct B;
struct C;

struct A
{
    std::unique_ptr<B> b_ptr_;

    ~A();
};

struct B
{
    std::unique_ptr<C> c_ptr_;

    ~B();
};

struct C
{
    std::unique_ptr<A> a_ptr_;

    ~C();
};


A::~A()
{
    std::cout << "~A()\n";
}

B::~B()
{
    std::cout << "~B()\n";
}

C::~C()
{
    std::cout << "~C()\n";
}

int
main()
{
    std::unique_ptr<A> a_ptr(new A);
    a_ptr->b_ptr_ = std::unique_ptr<B>(new B);
    a_ptr->b_ptr_->c_ptr_ = std::unique_ptr<C>(new C);
    a_ptr->b_ptr_->c_ptr_->a_ptr_ = std::move(a_ptr);
}

编译运行时,这个程序没有输出。这令人担忧,因为ABC 的析构函数没有运行,这表明存在内存泄漏。

有很多方法可以解决循环所有权问题。但第一步是了解您的内存所有权图,并确保它是非循环的,对于unique_ptr,还必须确保图中的任何节点都只有一个(拥有)父节点。

这是打破此示例所有权周期的一种方法:

#include <iostream>
#include <memory>

struct B;
struct C;

struct A
{
    std::unique_ptr<B> b_ptr_;

    ~A();
};

struct B
{
    std::unique_ptr<C> c_ptr_;

    ~B();
};

struct C
{
    A* a_ptr_;

    ~C();
};


A::~A()
{
    std::cout << "~A()\n";
}

B::~B()
{
    std::cout << "~B()\n";
}

C::~C()
{
    std::cout << "~C()\n";
}

int
main()
{
    std::unique_ptr<A> a_ptr(new A);
    a_ptr->b_ptr_ = std::unique_ptr<B>(new B);
    a_ptr->b_ptr_->c_ptr_ = std::unique_ptr<C>(new C);
    a_ptr->b_ptr_->c_ptr_->a_ptr_ = a_ptr.get();
}

正确输出:

~A()
~B()
~C()

即在循环中插入一个非拥有的“原始指针”。也可以使用shared_ptrweak_ptr 来打破循环:

#include <iostream>
#include <memory>

struct B;
struct C;

struct A
{
    std::shared_ptr<B> b_ptr_;

    ~A();
};

struct B
{
    std::shared_ptr<C> c_ptr_;

    ~B();
};

struct C
{
    std::weak_ptr<A> a_ptr_;

    ~C();
};


A::~A()
{
    std::cout << "~A()\n";
}

B::~B()
{
    std::cout << "~B()\n";
}

C::~C()
{
    std::cout << "~C()\n";
}

int
main()
{
    std::shared_ptr<A> a_ptr(new A);
    a_ptr->b_ptr_ = std::shared_ptr<B>(new B);
    a_ptr->b_ptr_->c_ptr_ = std::shared_ptr<C>(new C);
    a_ptr->b_ptr_->c_ptr_->a_ptr_ = std::move(a_ptr);
}

~A()
~B()
~C()

如果我误认为循环所有权是您的罪魁祸首,您的时间不会完全浪费在审查、理解和记录代码中的所有权路径上。

【讨论】:

  • 感谢霍华德,但代码中没有循环所有权。该应用程序在 Mac 上运行完美,但由于内存逐渐增长,iOS 版本运行一段时间后崩溃。它是一个 OpenGL ES 图形密集型应用程序,所以我真的不能忽视内存问题。
猜你喜欢
  • 2013-02-14
  • 2014-07-11
  • 2018-08-20
  • 2012-10-04
  • 2021-06-22
  • 2021-04-15
  • 2011-10-13
  • 2019-02-09
  • 1970-01-01
相关资源
最近更新 更多