Dynamic Memory Flashcards
How does std::share_ptr manage memory
We can think of a shared_ptr as if it has an associated counter, usually referred to as a reference count. Whenever we copy a shared_ptr, the count is incremented.
The counter is decremented when we assign a new value to the shared_ptr and when the shared_ptr itself is destroyed, such as when a local shared_ptr goes out of scope. Once a shared_ptr’s counter goes to zero, the shared_ptr automatically frees the object that it manages
How to initialize dyanmically allocated object in c++11
We can initialize a dynamically allocated object using direct initialization. We can use traditional construction (using parentheses), and under the new standard, we can also use list initialization (with curly braces):
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};
We can also value initialize a dynamically allocated object by following the type name with a pair of empty parentheses:
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
In the case of built-in types the difference is significant; a value-initialized object of built-in type has a well-defined value but a default-initialized object does not. Similarly, members of built-in type in classes that rely on the synthesized default constructor will also be uninitialized if those members are not initialized in the class body
Can we use auto to initialize a dynamically allocated object?
When we provide an initializer inside parentheses, we can use auto to deduce the type of the object we want to allocate from that initializer. However, because the compiler uses the initializer’s type to deduce the type to allocate, we can use auto only with a single initializer inside parentheses:
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
Does new throw? if so how may we avoid it?
If new is unable to allocate the requested storage, it throws an exception of type bad_alloc. We can prevent new from throwing an exception by using a different form of 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
This form of new is referred to as placement new.
What are common errors in managing memory dynamic ally with new and delete
- Forgetting to delete memory. Neglecting to delete dynamic memory is known as a “memory leak,”
- Using an object after it has been deleted.
- Deleting the same memory twice.
What is dangling pointer
When we delete a pointer, that pointer becomes invalid. Although the pointer is invalid, on many machines the pointer continues to hold the address of the (freed) dynamic memory. After the delete, the pointer becomes what is referred to as a dangling pointer. A dangling pointer is one that refers to memory that once held an object but no longer does so.
A fundamental problem with dynamic memory is that there can be several pointers that point to the same memory. Resetting the pointer we use to delete that memory lets us check that particular pointer but has no effect on any of the other pointers that still point at the (freed) memory.
What does an uninitialized smart pointer point to?
nullptr. If we do not initialize a smart pointer, it is initialized as a null pointer
Can we convert raw pointer to std::shared_ptr
The initialization of p1 implicitly asks the compiler to create a shared_ptr from the int* returned by new. Because we can’t implicitly convert a pointer to a smart pointer, this initialization is an error. For the same reason, a function that returns a shared_ptr cannot implicitly convert a plain pointer in its return statement
shared_ptr clone(int p) { return new int(p); // error: implicit conversion to shared_ptr }
Should we bind a raw pointer return from std:::shared_ptr::get to another std::shared_ptr ?
Although the compiler will not complain, it is an error to bind another smart pointer to the pointer returned by get:
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 In this case, both p and q point to the same memory. Because they were created independently from each other, each has a reference count of 1. When the block in which q was defined ends, q is destroyed. Destroying q frees the memory to which q points. That makes p into a dangling pointer, meaning that what happens when we attempt to use p is undefined. Moreover, when p is destroyed, the pointer to that memory will be deleted a second time.
What are the pre-cautions to be taken when using smart pointers
- 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
what does std::unique_ptr::release do?
// transfers ownership from p3 to p2 p2.reset(p3.release()); // reset deletes the memory to which p2 had pointed
The release member returns the pointer currently stored in the unique_ptr and makes that unique_ptr null. Thus, p2 is initialized from the pointer value that had been stored in p1 and p1 becomes null.
How to provide a custom function to delete the object
// p points to an object of type objT and uses an // object of type delT to free that object // it will call an object named fcn of type delT unique_ptr p (new objT, fcn);
Example: // when p is destroyed, the connection will be closed unique_ptr p(&c, end_connection);
Because decltype(end_connection) returns a function type, we must remember to add a * to indicate that we’re using a pointer to that type
Whats is a weak_ptr
A weak_ptr is a smart pointer that does not control the lifetime of the object to which it points. Instead, a weak_ptr points to an object that is managed by a shared_ptr. Binding a weak_ptr to a shared_ptr does not change the reference count of that shared_ptr.
auto p = make_shared(42); weak_ptr wp(p); // wp weakly shares with p; use count in p is unchanged
To access that object, we must call lock. The lock function checks whether the object to which the weak_ptr points still exists. If so, lock returns a shared_ptr to the shared object. As with any other shared_ptr, we are guaranteed that the underlying object to which that shared_ptr points continues to exist at least as long as that shared_ptr exists. For example:
if (shared_ptr np = wp.lock()) { // true if np is not null // inside the if, np shares its object with p }
Here we enter the body of the if only if the call to lock succeeds. Inside the if, it is safe to use np to access that object.
How to allocate an array of objects
// call get_size to determine how many ints to allocate int *pia = new int[get_size()]; // pia points to the first of these ints
We can also allocate an array by using a type alias to represent an array type. In this case, we omit the brackets:
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
Ho to initialize an array of dynamically allocated objects
We can value initialize the elements in an array by following the size with an empty pair of parentheses.
int *pia = new int[10]; // block of ten uninitialized ints
int *pia2 = new int10; // block of ten ints value initialized to 0
string *psa = new string[10]; // block of ten empty strings
string *psa2 = new string10; // block of ten empty strings
Under the new standard, we can also provide a braced list of element initializers:
// 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')};
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. In this case, new throws an exception of type bad_array_new_length. Like bad_alloc, this type is defined in the new header.
The fact that we cannot supply an initial value inside the parentheses means that we cannot use auto to allocate an array