C++ primer 第十二章-动态内存

Hi!这里是山幺幺的c++ primer系列。写这个系列的初衷是,虽然在学校学习了c++,但总觉得对这门语言了解不深,因此我想来啃啃著名的c++ primer,并在这里同步记录我的学习笔记。由于我啃的是英文版,所以笔记是中英文夹杂的那种。另外由于已有一定的编程基础,所以这个系列不会含有太基础的知识,想入门的朋友最好自己啃书嘻嘻~

关于生存期


Global objects

  • allocated at program start-up
  • destroyed when the program ends

Local, automatic objects

  • created and destroyed when the block in which they are defined is entered and exited

Local static objects

  • allocated before their first use
  • destroyed when the program ends

Dynamically allocated objects

  • have a lifetime that is independent of where they are created; they exist until they are explicitly freed

关于内存


概述

  • Objects allocated in 静态内存和栈内存 are automatically created and destroyed by the compiler
  • 动态内存指的是堆内存

静态内存

  • 存储:local static objects、class static data members、variables defined outside any function
  • 静态内存中的 objects are allocated before they are used, and they are destroyed when the program ends

栈内存

  • 存储:nonstatic objects defined inside functions
  • 栈内存中的 objects exist only while the block in which they are defined is executing

堆内存(free store)

  • 存储:objects that they dynamically allocate,即objects that the program allocates at run time,由程序控制其生存期
  • 使用动态内存的时机:don’t know how many objects will be needed; don’t know the precise type of the needed objects; want to share data between several objects
  • Objects allocated on the free store are unnamed

直接管理动态内存


new

  • 作用:allocates, and optionally initializes, an object in dynamic memory and returns a pointer to that object
  • 初始化:默认情况下dynamically allocated objects are default initialized;a dynamically allocated const object must be initialized:对于const dynamic object of a class type that defines a default constructor,可以implicitly initialize,对于其他类型的对象,必须explicitly initialize
string *ps = new string; // initialized to empty string
int *pi = new int; // pi points to an uninitialized int

int *pi = new int(1024); // object to which pi points has value 1024
string *ps = new string(10, '9'); // *ps is "9999999999"
// vector with ten elements with values from 0 to 9
vector<int> *pv = new vector<int>{0,1,2,3,4,5,6,7,8,9};

string *ps1 = new string; // default initialized to the empty string
string *ps = new string(); // value initialized to the empty string
int *pi1 = new int; // default initialized; *pi1 is undefined
int *pi2 = new int(); // value initialized to 0; *pi2 is 0

auto p1 = new auto(obj); // p points to an object of the type of obj;that object is initialized from obj
auto p2 = new auto{a,b,c}; // error: must use parentheses for the initializer

// allocate and initialize a const int
const int *pci = new const int(1024);
// allocate a default-initialized const empty string
const string *pcs = new const string;
  • new失败
    • Once a program has used all of its available free store memory, new will fail
    • new失败会throw an exception of type bad_alloc
    • 可以禁止throw bad_alloc:使用placement new,向new传参数
    // if allocation fails, new returns a null pointer
    int *p1 = new int; // if allocation fails, new throws std::bad_alloc
    int *p2 = new (nothrow) int; // if allocation fails, new returns a null pointer
    

delete

  • 作用:takes a pointer to a dynamic object, destroys that object, and frees the associated memory
  • The pointer we pass to delete must either point to dynamically allocated memory or be a null pointer,否则结果未定义
int i, *pi1 = &i, *pi2 = nullptr;
double *pd = new double(33), *pd2 = pd;
delete i; // error: i is not a pointer
delete pi1; // undefined: pi1 refers to a local
delete pd; // ok
delete pd2; // undefined: the memory pointed to by pd2 was already freed
delete pi2; // ok: it is always ok to delete a null pointer
  • 可以delete const
const int *pci = new const int(1024);
delete pci; // ok: deletes a const object
  • 使用普通指针而不是智能指针时,一定要记得delete!
// factory returns a pointer to a dynamically allocated object
Foo* factory(T arg) {
  // process arg as appropriate
  return new Foo(arg); // caller is responsible for deleting this memory
}

void use_factory(T arg) {
  Foo *p = factory(arg);
  // use p but do not delete it
} // p goes out of scope and is destroyed, but the memory to which p points is not freed!
  • dangling pointer:delete一个指针后,它continues to hold the address of the (freed) dynamic memory,称为dangling pointer(悬空指针)
    • 一定要记得把它设为空指针,或者等到它马上要go out of scope之前再delete
    • 常见的bug:delete后产生悬空指针q
    int *p(new int(42)); // p points to dynamic memory
    auto q = p; // p and q point to the same memory
    delete p; // invalidates both p and q
    p = nullptr; // indicates that p is no longer bound to an object
    

smart pointer


性质

  • ensure that the objects to which they point are automatically freed when it is appropriate to do so
  • 是c++ 11的新特性
  • 三种智能指针(shared_ptr、unique_ptr、weak_ptr)都定义在头文件memory中
  • 默认情况下a pointer used to initialize a smart pointer must point to dynamic memory,但只要我们supply our own operation to use in place of delete,也可以用指向其他资源的指针来初始化智能指针
struct destination; // represents what we are connecting to
struct connection; // information needed to use the connection
connection connect(destination*); // open the connection
void disconnect(connection); // close the given connection
void end_connection(connection *p) { disconnect(*p); }
void f(destination &d /* other parameters */) {
  connection c = connect(&d);
  shared_ptr<connection> p(&c, end_connection);
  // use the connection
  // when f exits, even if by an exception, the connection will be properly closed
}
// 或者
void f(destination &d /* other needed parameters */) {
  connection c = connect(&d); // open the connection
  // when p is destroyed, the connection will be closed
  unique_ptr<connection, decltype(end_connection)*> p (&c, end_connection);
  // use the connection
  // when f exits, even if by an exception, the connection will be properly closed
}

PS:When p is destroyed, it won’t execute delete on its stored pointer. Instead, p will call end_connection on that pointer.

shared_ptr和unique_ptr都支持的操作

  • get()

    • 作用:返回一个内置指针,指向智能指针的管理对象
    • 使用场景:cases when we need to pass a built-in pointer to code that can’t use a smart pointer
    • 注意危险行为:the code that uses the return from get must not delete that pointer;never use get to initialize or assign to another smart pointer
    shared_ptr<int> p(new int(42)); // reference count is 1
    int *q = p.get(); // ok: but don't use q in any way that might delete its pointer
    { // new block
      // undefined: two independent shared_ptrs point to the same memory
      shared_ptr<int>(q);
    } // block ends, q is destroyed, and the memory to which q points is freed
    int foo = *p; // undefined; the memory to which p points was freed
    

    PS:因为p和q were created independently from each other,所以它们的reference count 都是1

shared_ptr

  • 特点:允许多个指针指向同一对象
  • 初始化
    • 栗子
    shared_ptr<string> p1; // shared_ptr that can point at a string
    shared_ptr<list<int>> p2; // shared_ptr that can point at a list of ints
    shared_ptr<int> p2(new int(42)); // p2 points to an int with value 42
    shared_ptr<int> p1 = new int(1024); // error: must use direct initialization
    
    • 默认初始化的shared_ptr初始值为null pointer
    • 智能指针的参数是指针的构造函数是explicit的,所以不能implicitly convert a built-in pointer to a smart pointer,只能使用direct initialization
    shared_ptr<int> clone(int p) {
      return new int(p); // error: implicit conversion to shared_ptr<int>
      return shared_ptr<int>(new int(p)); // ok: explicitly create a shared_ptr<int> from int*
    }
    
  • 只有shared_ptr支持的操作
  • make_shared

    • 定义在头文件memory中
    • 功能:allocates and initializes an object in dynamic memory and returns a shared_ptr that points to that object
    • 特点:像emplace一样,make_shared uses its arguments to construct an object of the given type
    • 栗子
    // shared_ptr that points to an int with value 42
    shared_ptr<int> p3 = make_shared<int>(42);
    // p4 points to a string with value 9999999999
    shared_ptr<string> p4 = make_shared<string>(10, '9');
    // p5 points to an int that is value initialized to 0
    shared_ptr<int> p5 = make_shared<int>();
    
  • reset

    • 作用:assign a new pointer to a shared_ptr, updates the reference counts and, if appropriate, deletes the object to which p points
    • 栗子
    p = new int(1024); // error: cannot assign a pointer to a shared_ptr
    p.reset(new int(1024)); // ok: p points to a new object
    
    if (!p.unique())
      p.reset(new string(*p)); // we aren't alone; allocate a new copy
    *p += newVal; // now that we know we're the only pointer, okay to change this object
    
  • shared_ptr的复制和赋值

    • reference count
      1. associates with a shared_ptr的指向对象,记录how many other shared_ptrs point to the same object
      2. 智能指针p指向对象的reference count++的情形:用p初始化另一个智能指针时、use it as the right -hand operand of an assignment时、pass it to或return it from a function by value时
      3. 智能指针p指向对象的reference count--的情形:assign a new value to p时、p被destroy时(such as when a local shared_ptr goes out of scope)
      4. Once a shared_ptr’s counter goes to zero, the shared_ptr automatically frees the object that it manages
      • 栗子
      auto r = make_shared<int>(42); // int to which r points has one user
      r = q; // assign to r, making it point to a different address
      // increase the use count for the object to which q points
      // reduce the use count of the object to which r had pointed
      // the object r had pointed to has no users; that object is automatically freed
      
      • 另一个栗子
      // factory returns a shared_ptr pointing to a dynamically allocated object
      shared_ptr<Foo> factory(T arg) {
        // process arg as appropriate
        return make_shared<Foo>(arg);
      }
      // 情形1
      void use_factory(T arg) {
        shared_ptr<Foo> p = factory(arg);
        // use p
      } // p goes out of scope; the memory to which p points is automatically freed
      // 情形2
      void use_factory(T arg) {
        shared_ptr<Foo> p = factory(arg);
        return p; // reference count is incremented when we return p
      } // p goes out of scope; the memory to which p points is not freed
      
  • 使用智能指针也可能出现不需要某对象但未释放其内存的特殊情况:把shared_ptr放在容器中时。所以,If you put shared_ptrs in a container, and you subsequently need to use some, but not all, of the elements, remember to erase the elements you no longer need.(erase之后reference count--,shared_ptr就会自动释放内存了)

  • 把普通指针转化为智能指针后,不要再用普通指针访问智能指针管理的内存:下面这段代码中we passed a temporary shared_ptr to process. That temporary is destroyed when the expression in which the call appears finishes. 临时变量destroyed后,reference count 变为0,内存被释放,x变为悬空指针

// ptr is created and initialized when process is called
void process(shared_ptr<int> ptr) {
  // use ptr
} // ptr goes out of scope and is destroyed

int *x(new int(1024)); // dangerous: x is a plain pointer, not a smart pointer
process(x); // error: cannot convert int* to shared_ptr<int>
process(shared_ptr<int>(x)); // legal, but the memory will be deleted!
int j = *x; // undefined: x is a dangling pointer!

unique_ptr

  • 特点:“owns” the object to which it points;only one unique_ptr at a time can point to a given object
  • 只有unique_ptr支持的操作
  • 初始化:只能使用直接初始化
unique_ptr<double> p1; // unique_ptr that can point at a double
unique_ptr<int> p2(new int(42)); // p2 points to int with value 42
  • 不支持复制和赋值,除非它即将被destroy(这时,the compiler does a special kind of “copy”,在第十三章会讲)
unique_ptr<string> p1(new string("Stegosaurus"));
unique_ptr<string> p2(p1); // error: no copy for unique_ptr
unique_ptr<string> p3;
p3 = p2; // error: no assign for unique_ptr

unique_ptr<int> clone(int p) {
  // ok: explicitly create a unique_ptr<int> from int*
  return unique_ptr<int>(new int(p));
}

unique_ptr<int> clone(int p) {
  unique_ptr<int> ret(new int (p));
  // . . .
  return ret; // ok: return a copy of a local object
}
  • 支持transfer ownership
// transfers ownership from p1 (which points to the string Stegosaurus) to p2
unique_ptr<string> p2(p1.release()); // release makes p1 null
unique_ptr<string> p3(new string("Trex"));
// transfers ownership from p3 to p2
p2.reset(p3.release()); // reset deletes the memory to which p2 had pointed

p2.release(); // WRONG: p2 won't free the memory and we've lost the pointer
auto p = p2.release(); // ok, but we must remember to delete(p)

weak_ptr

  • 特点:does not control the lifetime of the object to which it points,而是指向an object that is managed by a shared_ptr;最大作用在于协助shared_ptr工作,像旁观者那样观测资源的使用情况;不可使用* 和 ->访问对象
  • 注意:binding a weak_ptr to a shared_ptr does not change the reference count of that shared_ptr;Once the last shared_ptr pointing to the object goes away, the object itself will be deleted,即使there are weak_ptrs pointing to it
  • 只有weak_ptr支持的操作
  • 初始化:用shared_ptr或者另一个weak_ptr对象
auto p = make_shared<int>(42);
weak_ptr<int> wp(p); // wp weakly shares with p; use count in p is unchanged
  • 访问元素:不能直接访问(因为可能被delete了),用lock:若元素仍存在,返回一个shared_ptr to the shared object(在lock()成功时会延长shared_ptr对象的生命周期,因为它递增了一个引用计数)
if (shared_ptr<int> np = wp.lock()) { // true if np is not null
  // inside the if, np shares its object with p
}

智能指针与exception

  • 智能指针可以避免exception带来的内存泄露隐患:下面这段代码中,若an exception happens between the new and the delete, and is not caught inside f, then this memory can never be freed
void f() {
  int *ip = new int(42); // dynamically allocate a new object
  // code that throws an exception that is not caught inside f
  delete ip; // free the memory before exiting
}

智能指针使用注意事项总结

  • Don’t use the same built-in pointer value to initialize (or reset) more than one smart pointer
  • Don’t delete the pointer returned from get()
  • Don’t use get() to initialize or reset another smart pointer
  • If you use a pointer returned by get(), remember that the pointer will become invalid when the last corresponding smart pointer goes away
  • If you use a smart pointer to manage a resource other than memory
    allocated by new, remember to pass a deleter

使用智能指针的经典栗子


意图

  • 之前使用的类,比如vector,allocate resources that exist only as long as the corresponding objects(比如When we copy a vector, the elements in the original vector and in the copy are separate from one another)
  • 现在我们希望可以让两个类真正地共享一个对象,因为可能出现下面的情形,所以这个对象必须在堆内存中
Blob<string> b1; // empty Blob
{ // new scope
  Blob<string> b2 = {"a", "an", "the"};
  b1 = b2; // b1 and b2 share the same elements
} // b2 is destroyed, but the elements in b2 must not be destroyed,所以elements必须在堆中,否则其生存期会随着scope的结束而结束

类的定义

class StrBlob {
public:
  typedef std::vector<std::string>::size_type size_type;
  StrBlob();
  StrBlob(std::initializer_list<std::string> il);
  size_type size() const { return data->size(); }
  bool empty() const { return data->empty(); }
  void push_back(const std::string &t) {data->push_back(t);}
  void pop_back();
  std::string& front();
  std::string& back();
private:
  std::shared_ptr<std::vector<std::string>> data;
  // throws msg if data[i] isn't valid
  void check(size_type i, const std::string &msg) const;
};

构造函数

StrBlob::StrBlob(): data(make_shared<vector<string>>()) { }
StrBlob::StrBlob(initializer_list<string> il): data(make_shared<vector<string>>(il)) { }

一些成员函数

void StrBlob::check(size_type i, const string &msg) const {
  if (i >= data->size())
  throw out_of_range(msg);
}
string& StrBlob::front()
{
// if the vector is empty, check will throw
check(0, "front on empty StrBlob");
return data->front();
}
string& StrBlob::back() {
  check(0, "back on empty StrBlob");
  return data->back();
}
void StrBlob::pop_back() {
  check(0, "pop_back on empty StrBlob");
  data->pop_back();
}

使用这个类

StrBlob b1;
{
  StrBlob b2 = {"a", "an", "the"};
  b1 = b2;
  b2.push_back("about");
}

Dynamic Arrays


注意

  • dynamic array并不是array类型的,所以不支持begin\end\for这些

使用new

  • 作用:allocates the requested number of objects and (assuming the allocation succeeds) returns a pointer to the first one
  • 栗子:get_size()返回的值必须是integral type,但可以不是const
// call get_size to determine how many ints to allocate
int *pia = new int[get_size()]; // pia points to the first of these ints

typedef int arrT[42]; // arrT names the type array of 42 ints
int *p = new arrT; // allocates an array of 42 ints; p points to the first one
  • 初始化:If there are fewer initializers than elements, the remaining elements are value initialized;If there are more initializers than the given size, then the new expression fails and no storage is allocated 而且会抛出bad_array_new_length异常
int *pia = new int[10]; // block of ten uninitialized ints
int *pia2 = new int[10](); // block of ten ints value initialized to 0
string *psa = new string[10]; // block of ten empty strings
string *psa2 = new string[10](); // block of ten empty strings

// block of ten ints each initialized from the corresponding initializer
int *pia3 = new int[10]{0,1,2,3,4,5,6,7,8,9};
// block of ten strings; the first four are initialized from the given initializers
// remaining elements are value initialized
string *psa3 = new string[10]{"a", "an", "the", string(3,'x')};
  • 注意

    • we cannot use auto to allocate an array
    • classes that do not have default constructors cannot be dynamically allocated as an array
    • 长度为0:Calling new[n] with n equal to 0 is legal even though we cannot create an array variable of size 0;下面代码的第二句:cp是一个valid, nonzero的指针,相当于长度为0的array的off-the-end pointer
    char arr[0]; // error: cannot define a zero-length array
    char *cp = new char[0]; // ok: but cp can't be dereferenced
    
  • 内存的释放

    • 格式
    delete [] pa; // pa must point to a dynamically allocated array or be null
    delete pa; // 若pa point to a dynamically allocated array,则结果undefined
    
    • Elements in an array are destroyed in reverse order
  • 使用unique_ptr管理动态数组

    • 栗子
    // up points to an array of ten uninitialized ints
    unique_ptr<int[]> up(new int[10]);
    up.release(); // automatically uses delete[] to destroy its pointer
    
    • 指向数组时,不能用.和->
    • 操作一览
  • 使用shared_ptr管理动态数组

    • shared_ptr并不直接支持动态数组,需要我们提供deleter
    // to use a shared_ptr we must supply a deleter
    shared_ptr<int> sp(new int[10], [](int *p) { delete[] p; });
    sp.reset(); // uses the lambda we supplied that uses delete[] to free the array
    
    • 遍历数组
    // shared_ptrs don't have subscript operator and don't support pointer arithmetic
    for (size_t i = 0; i != 10; ++i)
      *(sp.get() + i) = i; // use get to get a built-in pointer
    

使用allocator Class

  • 优势:用new会将初始化和分配内存绑定在一起完成,而用allocator Class可以decouple memory allocation from object construction,从而
    • 避免浪费:new expression allocates and initializes n strings但是we might not need n strings;elements are written twice: first when the elements are default initialized, and subsequently when we assign to them
    string *const p = new string[n]; // construct n empty strings
    string s;
    string *q = p; // q points to the first string
    while (cin >> s && q != p + n)
      *q++ = s;
    
    • 避免“使用new T[]时,T必须有默认构造函数”的限制
  • 操作一览
  • 栗子
allocator<string> alloc; // object that can allocate strings
auto const p = alloc.allocate(n); // allocate n unconstructed strings

auto q = p; // q will point to one past the last constructed element
alloc.construct(q++); // *q is the empty string
alloc.construct(q++, 10, 'c'); // *q is cccccccccc
alloc.construct(q++, "hi"); // *q is hi!
  • 不要访问unconstructed memory
cout << *p << endl; // ok: uses the string output operator
cout << *q << endl; // disaster: q points to unconstructed memory!
  • destroy与内存释放
    • We may destroy only elements that are actually constructed
    • destroy后,我们可以reuse the memory to hold other strings或者return the memory to the system
    • deallocate中的指针不能为空指针,必须指向allocate分配的内存;deallocate中的数字必须be the same size as used in the call to allocate
while (q != p)
  alloc.destroy(--q); // free the strings we actually allocated

alloc.deallocate(p, n);
  • construct objects in uninitialized memory的算法

    • 算法一览
    • 栗子
    // allocate twice as many elements as vi holds
    auto p = alloc.allocate(vi.size() * 2);
    // construct elements starting at p as copies of elements in vi
    auto q = uninitialized_copy(vi.begin(), vi.end(), p);
    // initialize the remaining elements to 42
    uninitialized_fill_n(q, vi.size(), 42);
    

补充:变量存储


https://blog.csdn.net/hackerain/article/details/7953261

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,743评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,296评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,285评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,485评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,581评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,821评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,960评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,719评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,186评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,516评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,650评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,329评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,936评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,757评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,991评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,370评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,527评论 2 349