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 *pv = new vector{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 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 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 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(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 p1; // shared_ptr that can point at a string
    shared_ptr> p2; // shared_ptr that can point at a list of ints
    shared_ptr p2(new int(42)); // p2 points to an int with value 42
    shared_ptr 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 clone(int p) {
      return new int(p); // error: implicit conversion to shared_ptr
      return shared_ptr(new int(p)); // ok: explicitly create a shared_ptr 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 p3 = make_shared(42);
    // p4 points to a string with value 9999999999
    shared_ptr p4 = make_shared(10, '9');
    // p5 points to an int that is value initialized to 0
    shared_ptr p5 = make_shared();
    
  • 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(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 factory(T arg) {
        // process arg as appropriate
        return make_shared(arg);
      }
      // 情形1
      void use_factory(T arg) {
        shared_ptr 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 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 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
process(shared_ptr(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 p1; // unique_ptr that can point at a double
unique_ptr p2(new int(42)); // p2 points to int with value 42
  • 不支持复制和赋值,除非它即将被destroy(这时,the compiler does a special kind of “copy”,在第十三章会讲)
unique_ptr p1(new string("Stegosaurus"));
unique_ptr p2(p1); // error: no copy for unique_ptr
unique_ptr p3;
p3 = p2; // error: no assign for unique_ptr

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

unique_ptr clone(int p) {
  unique_ptr 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 p2(p1.release()); // release makes p1 null
unique_ptr 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(42);
weak_ptr 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 np = wp.lock()) { // true if np is not null
  // inside the if, np shares its object with p
}
  • 使用场景:循环引用,这篇文章写得很详细
    https://blog.csdn.net/albertsh/article/details/82286999

智能指针与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 b1; // empty Blob
{ // new scope
  Blob 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::size_type size_type;
  StrBlob();
  StrBlob(std::initializer_list 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> data;
  // throws msg if data[i] isn't valid
  void check(size_type i, const std::string &msg) const;
};

构造函数

StrBlob::StrBlob(): data(make_shared>()) { }
StrBlob::StrBlob(initializer_list il): data(make_shared>(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 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 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 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

你可能感兴趣的:(C++ primer 第十二章-动态内存)