内存管理

本文探讨C++中的内存管理技巧,包括内存泄漏的原因及预防,使用智能指针如unique_ptr减少内存泄漏风险,以及构造函数、析构函数的作用。文章还介绍了new和delete操作符的正确使用,以及如何在类层次中实现拷贝写优化,最后讨论了C++中垃圾回收的概念。

内存管理

内存泄漏

内存泄漏的严重性和付出的努力无关,只与使用的方法有关

关键

  • 在更易管理的类型中隐藏分配和释放
    • 单个对象
      • 使用make_unique 或 make_shared
    • 多个对象
      • 使用标准容器
        • vector、无序map
      • 对字符串来说,使用string,比自己操作内存好
  • 减少显式分配和解除分配的数量,使剩余的示例更容易跟踪。
  • 缺少显式内存管理,宏,强制转换,溢出检查,显式大小限制和指针
  • 如果您无法在应用程序中隐式处理分配/释放作为对象的一部分,则可以使用资源句柄来最小化泄漏的可能性

禁止

delete 已经delete 的内存

new/delete 和 malloc/free混用

在new 的指针上使用realloc

new、malloc区别

首先考虑使用make_unique 管理内存

malloc

  • 参数为一个内存大小,返回void* ,数据未被初始化
  • 通过返回0 来表示内存申请失败

new

  • 参数为一个类型,加数量,加可选的,初始化参数,返回可选的初始化了的内存
  • 通过抛出bad_alloc 来报告申请失败

cpp 中更推荐使用new,而不是malloc

原因

  • 构造函数/析构函数
  • 类型安全
  • 重载
    • new 操作可以被重载

cpp 中为什么没有类似realloc 的功能

使用标准容器,比如vector来让其增长是更好的选择

当然可以使用realloc,那么就舍弃了new 操作带来的所有好处,只取了realloc 带来的好处

我是否应该在new 后,检查返回的是否为NULL

不需要

auto p = make_unique(); // No need to check if p is null

相应的,delete 之前也不需要检查

  • 为null什么都不会做

new 和 delete 的使用细节

如何将老代码转换为自动检查new 是否返回null

#include <new>       // To get std::set_new_handler
#include <cstdlib>   // To get abort()
#include <iostream>  // To get std::cerr
class alloc_error : public std::exception {
public:
  alloc_error() : exception() { }
};
void myNewHandler()
{
  // This is your own handler.  It can do anything you want.
  throw alloc_error();
}
int main()
{
  std::set_new_handler(myNewHandler);   // Install your "new handler"
  // ...
}
  • 一些命名空间范围/全局/静态对象的构建函数使用了new,可能不会调用到,因为它们比main 先执行

new

  • p = new Fred()中,如果Fred的构造函数抛出异常,是否会产生内存泄漏
    • 不会
// Original code: Fred* p = new Fred();
Fred* p;
void* tmp = operator new(sizeof(Fred));
try {
  new(tmp) Fred();  // Placement new
  p = (Fred*)tmp;   // The pointer is assigned only if the ctor succeeds
}
catch (...) {
  operator delete(tmp);  // Deallocate the memory
  throw;                 // Re-throw the exception
}

delete

  • delete 时的两个步骤
 // Original code: delete p;
if (p) {    // or "if (p != nullptr)"
  p->~Fred();
  operator delete(p);
}
  • 成员函数delete this是否合法
    • 首先确保自己是被new出来的,排出new []
    • 此函数后不会再对此对象进行操作
    • 确保成员函数的其它部分不会触及此对象的任何部分
    • 子主题 4

如何强制令我的类只能通过new 生成

class Fred {
public:
  // The create() methods are the "named constructors":
  static Fred* create()                 { return new Fred();     }
  static Fred* create(int i)            { return new Fred(i);    }
  static Fred* create(const Fred& fred) { return new Fred(fred); }
  // ...
private:
  // The constructors themselves are private or protected:
  Fred();
  Fred(int i);
  Fred(const Fred& fred);
  // ...
};

友元函数可以替代这里的静态成员函数

简单的引用计数实现

class FredPtr;
class Fred {
public:
  Fred() : count_(0) /*...*/ { }  // All ctors set count_ to 0 !
  // ...
private:
  friend class FredPtr;     // A friend class
  unsigned count_;
  // count_ must be initialized to 0 by all constructors
  // count_ is the number of FredPtr objects that point at this
};
class FredPtr {
public:
  Fred* operator-> () { return p_; }
  Fred& operator* ()  { return *p_; }
  FredPtr(Fred* p)    : p_(p) { ++p_->count_; }  // p must not be null
 ~FredPtr()           { if (--p_->count_ == 0) delete p_; }
  FredPtr(const FredPtr& p) : p_(p.p_) { ++p_->count_; }
  FredPtr& operator= (const FredPtr& p)
        { // DO NOT CHANGE THE ORDER OF THESE STATEMENTS!
          // (This order properly handles self-assignment)
          // (This order also properly handles recursion, e.g., if a Fred contains FredPtrs)
          Fred* const old = p_;
          p_ = p.p_;
          ++p_->count_;
          if (--old->count_ == 0) delete old;
          return *this;
        }
private:
  Fred* p_;    // p_ is never NULL
};

下面更加强化这种封装性

class Fred {
public:
  static FredPtr create();              // Defined below class FredPtr {...};
  static FredPtr create(int i, int j);  // Defined below class FredPtr {...};
  // ...
private:
  Fred();
  Fred(int i, int j);
  // ...
};
class FredPtr { /* ... */ };
inline FredPtr Fred::create()             { return new Fred(); //隐式构建FredPtr对象}
inline FredPtr Fred::create(int i, int j) { return new Fred(i,j); }

通过引用计数实现,拷贝写

class Fred {
public:
  Fred();                               // A default constructor
  Fred(int i, int j);                   // A normal constructor
  Fred(const Fred& f);
  Fred& operator= (const Fred& f);
 ~Fred();
  void sampleInspectorMethod() const;   // No changes to this object
  void sampleMutatorMethod();           // Change this object
  // ...
private:
  class Data {
  public:
    Data();
    Data(int i, int j);
    Data(const Data& d);
    // Since only Fred can access a Fred::Data object,
    // you can make Fred::Data's data public if you want.
    // But if that makes you uncomfortable, make the data private
    // and make Fred a friend class via friend class Fred;
    // ...your data members are declared here...
    unsigned count_;
    // count_ is the number of Fred objects that point at this
    // count_ must be initialized to 1 by all constructors
    // (it starts as 1 since it is pointed to by the Fred object that created it)
  };
  Data* data_;
};
Fred::Data::Data()              : count_(1) /*init other data*/ { }
Fred::Data::Data(int i, int j)  : count_(1) /*init other data*/ { }
Fred::Data::Data(const Data& d) : count_(1) /*init other data*/ { }
Fred::Fred()             : data_(new Data()) { }
Fred::Fred(int i, int j) : data_(new Data(i, j)) { }
Fred::Fred(const Fred& f)
  : data_(f.data_)
{
  ++data_->count_;
}
Fred& Fred::operator= (const Fred& f)
{
  // DO NOT CHANGE THE ORDER OF THESE STATEMENTS!
  // (This order properly handles self-assignment)
  // (This order also properly handles recursion, e.g., if a Fred::Data contains Freds)
  Data* const old = data_;
  data_ = f.data_;
  ++data_->count_;
  if (--old->count_ == 0) delete old;
  return *this;
}
Fred::~Fred()
{
  if (--data_->count_ == 0) delete data_;
}
void Fred::sampleInspectorMethod() const
{
  // This method promises ("const") not to change anything in *data_
  // Other than that, any data access would simply use "data_->..."
}
void Fred::sampleMutatorMethod()
{
  // This method might need to change things in *data_
  // Thus it first checks if this is the only pointer to *data_
  if (data_->count_ > 1) {
    Data* d = new Data(*data_);    // Invoke Fred::Data's copy ctor
    --data_->count_;
    data_ = d;
  }
  assert(data_->count_ == 1);
  // Now the method proceeds to access "data_->..." as normal
}

在类层次中使用拷贝写


class Fred {
public:
    static Fred create1(const std::string& s, int i);
    static Fred create2(float x, float y);
    Fred(const Fred& f);
    Fred& operator= (const Fred& f);
    ~Fred();
    void sampleInspectorMethod() const;   // 不修改
    void sampleMutatorMethod();           // 修改
                                          // ...
private:
    class Data {
    public:
        Data() : count_(1) { }
        Data(const Data& d) : count_(1) { }              // 不拷贝count_
        Data& operator= (const Data&) { return *this; }  // 不拷贝count_
        virtual ~Data() { assert(count_ == 0); }         // 虚析构函数
        virtual Data* clone() const = 0;                 // 虚构造函数
        virtual void sampleInspectorMethod() const = 0;  // 纯虚函数
        virtual void sampleMutatorMethod() = 0;
    private:
        unsigned count_;   // count_ doesn't need to be protected
        friend class Fred; // Allow Fred to access count_
    };
    class Der1 : public Data {
    public:
        Der1(const std::string& s, int i);
        virtual void sampleInspectorMethod() const;
        virtual void sampleMutatorMethod();
        virtual Data* clone() const;
        // ...
    };
    class Der2 : public Data {
    public:
        Der2(float x, float y);
        virtual void sampleInspectorMethod() const;
        virtual void sampleMutatorMethod();
        virtual Data* clone() const;
        // ...
    };
    Fred(Data* data);
    // Creates a Fred smart-reference that owns *data
    // It is private to force users to use a createXXX() method
    // Requirement: data must not be NULL
    Data* data_;   // Invariant: data_ is never NULL
};
Fred::Fred(Data* data) : data_(data) { assert(data != nullptr); }
Fred Fred::create1(const std::string& s, int i) { return Fred(new Der1(s, i)); }
Fred Fred::create2(float x, float y) { return Fred(new Der2(x, y)); }
Fred::Data* Fred::Der1::clone() const { return new Der1(*this); }
Fred::Data* Fred::Der2::clone() const { return new Der2(*this); }
Fred::Fred(const Fred& f)
    : data_(f.data_)
{
    ++data_->count_;
}
Fred& Fred::operator= (const Fred& f)
{
    // DO NOT CHANGE THE ORDER OF THESE STATEMENTS!
    // (This order properly handles self-assignment)
    // (This order also properly handles recursion, e.g., if a Fred::Data contains Freds)
    Data* const old = data_;
    data_ = f.data_;
    ++data_->count_;
    if (--old->count_ == 0) delete old;
    return *this;
}
Fred::~Fred()
{
    if (--data_->count_ == 0) delete data_;
}
void Fred::sampleInspectorMethod() const
{
    // This method promises ("const") not to change anything in *data_
    // Therefore we simply "pass the method through" to *data_:
    data_->sampleInspectorMethod();
}
void Fred::sampleMutatorMethod()
{
    // 拷贝写
    if (data_->count_ > 1) {
        Data* d = data_->clone();   // 虚构造,让子类决定如何拷贝自己
        --data_->count_;
        data_ = d;
    }
    assert(data_->count_ == 1);
    // Now we "pass the method through" to *data_:
    data_->sampleMutatorMethod();
}

是否可以绝对阻止人们破坏引用计数机制

有两个暗中破坏引用计数机制的途径

  1. 有人通过Fred* 而不是FrePtr 进行操作。如果FredPtr 类有一个operator*() 方法,返回Fred&。
FredPtr p = Fred::create();
Fred* p2 = &*p;

避免方法,重载Fred::operator&() 使其返回一个FredPtr,或修改FredPtr::operator*() 的返回类型,让其返回FredRef,它需要有Fred 的所有行为,并将调用转发给真正的Fred;或者,直接砍掉FredPtr::operator*(),这也失去了使用Fred& 的能力。但即使这样,客户仍然可能通过operator->得到Fred*:

FredPtr p = Fred::create();
Fred* p2 = p.operator->();
  1. Fred 可能是安全的,此时,FredPtr可能泄露和/或 悬挂,FredPtr 可能不安全。2.1 通过new 创建FredPtr可能造成内存泄露,避免方法,将FredPtr::operator new()设置为私有2.2 创建FredPtr本地对象后,通过& 符号得到FredPtr指针,之后,非法引用,方法:FredPtr::operator&() 私有。2.3 FredPtr p;FredPtr &q = p;//此时又泄露了

即使我们补上了所有的漏洞,C++ 有一系列的语法,称为指针转换,无法避免的导致问题。
经验:

1… 无论如何努力,无法躲过间谍
2… 可以尽量的避免错误的出现

推荐:使用易于构建且易于使用的机制来防止错误,并且不要试图阻止间谍活动。

因此,如果我们不能使用C++ 语言来阻止间谍活动,还有其它方法吗?是,使用code review。间谍技术通常涉及一些奇怪的语法和/或使用指针转换和联合,可以用工具来指出大多数“热点”。

c++ 如何使用垃圾回收

C++ Programming Laguage和site for c and c++ garbage collection 中有相关的介绍。http://www.stroustrup.com/4th.html
http://www.hpl.hp.com/personal/Hans_Boehm/gc
垃圾回收:http://www.iecc.com/gclist/GC-faq.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值