[C++ Primer] C12(重点章节)

Chapter 12. Dynamic Memory


Difference of stack and heap in c++

  1. stack stores all the local variables created by the function, and follow the LIFO strategy. Once the function call is done, all function data and variables are freed up. Stack is a contiguous block of memory that is managed automatically by the operating system.

  2. stack has a limited size, and if the program tries to allocate more memory, it will cause a stack overflow.

  3. heap is a region of memory that is managed manually by the programmer. Memory is allocated on the heap using dynamic memory allocation functions like ‘new’ or ‘malloc’. The memory allocated on the heap until it’s explicitly deallocated by the programmer.

  4. heap has a much larger size compared to the stack. However, managing memory on the heap can be more complex and error-prone compared to managing memory on the stack.

  • Summary:
      1. Memory allocation on the stack is managed automatically by the operating system, while memory allocation on the heap is managed manually by the programmer.
      1. The stack has a limited size, while the heap has a much larger size.
      1. Memory allocated on the stack is automatically deallocated when the function returns, while memory allocated on the heap remains allocated until it is explicitly deallocated by the programmer.

12.1. Dynamic Memory and Smart Pointers

Dynamically allocated objects have a lifetime that is independent of where they are created; they exist until they are explicitly freed! But sometimes it will cause some bugs.

allocate in stack:
    int a;
    int b;
    ---
    
allocate in heap:
    int *a = new int();

To make using dynamic objects safer, there exist two smart pointer types that dynamically allocated objects.

Smart pointers ensure that the objects to which they point are automatically freed when it is appropriate to do so.

Smart pointers allocate memory on heap.

    1. shared_ptr, which allows multiple pointers to refer to the same object
    1. unique_ptr, which “owns” the object to which it points.

common operations to shared_ptr and unique_ptr:

    1. shared_ptr sp; unique_ptr up; Both are null smart pointer that can point to objects of Type T.
    1. p, use p as a condition, true if p points to an object; *p, dereference p to get the object to which p points.
  • swap(p,q): swap the pointers in p and q

12.1.1. The shared_ptr Class

operations:

    1. make_shared(args): return a shared_ptr pointing to a dynamically allocated object of Type T.
    1. shared_ptrp(q): p is a copy of shared_ptr q; increments the count in q.
    1. p.unique(): return true if p.use_count() is one; false otherwise.
    1. p.use_count(): return num of objects sharing with p
// shared_ptr taht points to an int with value 1
shared_ptr<int> p = make_shared<int>(1);
// q points to a string with value 999
shared_ptr<string> q = make_shared<string>(3,'9');
// r points to an int that is initialized to 0
shared_ptr<int> r = make_shared<int>();
// s points to the same object as r points to
shared_ptr<int> s(r);

cout << p.use_count() << endl; // 1
cout << r.use_count() << endl; // 2
{
    shared_ptr<int> g(r);
    cout << g.use_count() << endl; // 3
    cout << r.use_count() << endl; // 3
} // g is destroyed 

cout << r.use_count() << endl; // 2

12.1.2. Managing Memory Directly

shallow/deep copy

shallow copy:

class A{
public:
    int* a_;
    string b_;
    A(int a, string b) : a_(new int(a)), b_(b) {
        cout << "in constructor" << endl; 
    }
    ~A() {
        cout << "in destructor" << b_ << endl;
        delete a_;
    }
};

void func() {
    A a1(1,"a1");
    A a2(a1); // copy construction // what we done is shallow copy, a1 and a2 points to the same object
    a2.b_ = "a2";

    cout << a1.a_ << endl;
    cout << a2.a_ << endl;
    cout << "123" <<endl;
}

int main() {
    func();
    cout << "hello" << endl;
    return 0;
}

// in constructor
// 0x882770
// 0x882770
// 123
// in destructora2
// in destructora1
// hello

deep copy:

class A{
public:
    int* a_;
    string b_;
    A (int a, string b) : a_(new int(a)), b_(b) {
        cout << "in constructor" << endl; 
    }
    A (const A& a) {
        // this->a_ = a->a_; // shallow copy will copy the value of the pointer address.
        this->a_ = new int(*(a.a_)); // deep copy will create a new object
    }
    ~A() {
        cout << "in destructor" << b_ << endl;
        delete a_;
    }
};

void func() {
    A a1(1,"a1");
    A a2(a1); // copy construction // what we done is shallow copy, a1 and a2 points to the same object
    a2.b_ = "a2";

    cout << a1.a_ << endl;
    cout << a2.a_ << endl;
    cout << "123" <<endl;
}

int main() {
    func();
    cout << "hello" << endl;
    return 0;
}

// in constructor
// 0x7e2770
// 0x7e27c0
// 123
// in destructora2
// in destructora1
// hello

We cannot implicitly convert a pointer to a smart pointer:

shared_ptr<int> p = new int(1); // error

shared_ptr<int> p2(new int(1)); // correct

It is dangerous to use a built-in pointer to access an object owned by a smart pointer, because we may not know when that object is destroyed.

dangling pointer 悬空指针

A dangling pointer is a pointer that points to memory that has been deallocated (freed) or otherwise no longer exists. Dereferencing a dangling pointer can result in undefined behavior, since the memory it points to may have been reallocated for another purpose, or the memory may have been completely released back to the operating system.

A* a = new A(1);

{
    shared_ptr<A> str(a); // use_count = 1
} // use_count = 0

cout << a.a_; // compiler will not print error, but the address of a is destroyed!

12.1.4. Smart Pointers and Exceptions:

class A {
public:
    int* a_;
    A(int a) : a_(new int(a)) {
        cout << "This is the constructor" << endl;
    }
    ~A() {
        delete a_;
        cout << "This is the destructor, you like it?" << endl;
    }
};

void throwException() {
    throw bad_exception();
}

void func() {
    std::shared_ptr<A> a = make_shared<A> (1);

    try {
        throwException();
    } catch(const exception& e) {
        cout << e.what() << endl;
    }

    // other operations
    int *abs = new int(2);
    *abs *= 2;
    //delete(abs);
} // shared_pte will be freed, but abs is stored in heap, not freed

int main() {
    func();
    cout << "hello world" << endl;
    return 0;
}

This is the constructor
std::bad_exception
This is the destructor, you like it?
hello world

what if we want our shared_pte enter into the self-made destructor, not the default ~A():

class A {
public:
    int* a_;
    A(int a) : a_(new int(a)) {
        cout << "This is the constructor" << endl;
    }
    ~A() {
        delete a_;
        cout << "This is the destructor, you like it?" << endl;
    }
};

void throwException() {
    throw bad_exception();
}

void self_made_destructor(A* a) {
    delete a->a_;
    cout << "In self_made destructor, you like it?" << endl;
}

void func() {
    // std::shared_ptr<A> a = make_shared<A> (1);
    shared_ptr<A> a(new A(1), self_made_destructor);
    try {
        throwException();
    } catch(const exception& e) {
        cout << e.what() << endl;
    }

    // other operations
    int *abs = new int(2);
    *abs *= 2;
    //delete(abs);
} // shared_pte will be freed, but abs is stored in heap, not freed

int main() {
    func();
    cout << "hello world" << endl;
    return 0;
}

This is the constructor
std::bad_exception
In self_made destructor, you like it?
hello world

Unique Pointers

A unique_ptr “owns” the object to which it points. Unlike shared_ptr, only one
unique_ptr at a time can point to a given object. The object to which a
unique_ptr points is destroyed when the unique_ptr is destroyed

specific operations:

unique_ptru1: Null unique_ptrs that can point to objects of type T. Use delete to free it’s pointer.

unique_ptr<T,D>u2: use a callable object of type D to free its pointer.

u = nullptr; make u null

u.reset(): deletes the object which u points.


unique_ptr p1;

unique_ptr p2(new int(1)); // p2 points to int with value 1

No assign and copy for unique_ptr:
u = u2; // error
unique_ptru2(u); // error

weak pointer

A weak_ptr is a smart pointer that does not control the lifetime of the object to which it points. Binding a weak_ptr to a shared_ptr does not change the reference count of that shared_ptr.

When we create a weak_ptr, we initialize it from a shared_ptr:

auto p = make_shared<int>(1);
weak_ptr<int> wp(p);

// w.expired(): return true if w.use_count() is zero, false otherwise.

// w.lock(): if expired is true, returns a null shared_ptr, otherwise return a shared_ptr to which objects w points.

12.2 Dynamic Arrays

How to allocate storage for many obejects at once? For example, vector and string.

Language method: new (int *p = new int[100])

Library: a template calss called allocator to seperate allocation from initialization.

when we use new to allocate an array, we do not get an object with an array type. Instead, we get a pointer to the element type of the array. The allocated memory does not have an array type, we cannot call begin or end on a dynamic array. It is important to remember that what we call a dynamic array does not have
an array type (In C++, an array type is a type that represents a fixed-size sequence of elements of the same type. It is a fundamental data structure in C++).

// Delete Dyncamic Array
int *p = new int[10];
delete [] p; // Use delete[] to deallocate an array of objects that was allocated using new[]
// delete pa; Use delete to deallocate a single object that was allocated using new

// The compiler is unlikely to warn us if we forget the brackets when we delete a pointer to an array or if we use them when we delete a pointer to an object.

Smart Pointers and Dynamic Arrays

// To use a unique_ptr to manage a dynamic array, we must include a pair of empty brackets after the object type:

unique_ptr<int[]> up(new int[10]);
up.release(); // automatically uses delete[] to destroy its pointer

to use a shared_ptr we must supply a deleter

std::shared_ptr<int[]> p = std::make_shared<int[]>(10);
shared_ptr<int> sp(new int[10], [](int* p) {delete [] p;});

"std::shared_ptr" has a default deleter built-in that knows how to delete objects created with new. Specifically, the default deleter uses the delete operator to deallocate the memory allocated with new. This means that the std::shared_ptr<int> returned by std::make_shared<int>(1) will automatically deallocate the int object when there are no more shared pointers referencing it.

12.2.2 The allocator Class

new and delete combines memory allocation/deallocation and object construction/deconstruction.

How to decouple memory allocation from object construction?

P.S: More importantly, classes that do not have default constructors cannot be
dynamically allocated as an array.

The allocator Class

allocator<T> a: Defines an allocator object named a that can allocate memory for objects of type T.

a.allocate(n): Allocates raw, unconstructed memory to hold n objects of Type T.

a.deallocate(p, n): Deallocate memory that held m object of Type T starting at the address in the T* pointer p.

a.construct(p, args)

a.destroy(p): run the destructor on the object
void func() {
    // create an allocator for std::string
    allocator<string> alloc;
    // allocate memory for an array of 10 strings
    auto const p = alloc.allocate(10);
    string* const pa = alloc.allocate(20);

    // construct strings in the allocated memory
    for (int i = 0; i < 10; i++) {
        alloc.construct(p+i, to_string(i));
    }
    // print array p
    for (int i = 0; i < 10; i++) {
        cout << *(p+i) << endl;
    }
    // destroy array p
    for (int i = 0; i <10; i++) {
        alloc.destroy(p+i);
    }
    // Though we destroy the object, the memory 
    // still contains the values that were previously 
    // stored in the object.
    for (int i = 0; i < 10; i++) {
        cout << *(p+i) << endl;
    } 
    // deallocate p;
    alloc.deallocate(p, 10);
}


int main() {
    func();
    cout << "hello world" << endl;
    return 0;
}

0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
hello world

Chapter Sum:

In C++, memory is allocated through expressions. The library also defines an
new allocator expressions and freed through class for allocating blocks of delete dynamic memory.

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值