Dynamic Memory Flashcards

1
Q

How does std::share_ptr manage memory

A

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 well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

How to initialize dyanmically allocated object in c++11

A

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

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

Can we use auto to initialize a dynamically allocated object?

A

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
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

Does new throw? if so how may we avoid it?

A

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.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q

What are common errors in managing memory dynamic ally with new and delete

A
  1. Forgetting to delete memory. Neglecting to delete dynamic memory is known as a “memory leak,”
  2. Using an object after it has been deleted.
  3. Deleting the same memory twice.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

What is dangling pointer

A

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.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

What does an uninitialized smart pointer point to?

A

nullptr. If we do not initialize a smart pointer, it is initialized as a null pointer

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

Can we convert raw pointer to std::shared_ptr

A

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
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

Should we bind a raw pointer return from std:::shared_ptr::get to another std::shared_ptr ?

A

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.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q

What are the pre-cautions to be taken when using smart pointers

A
  • 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
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q

what does std::unique_ptr::release do?

A
// 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 well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q

How to provide a custom function to delete the object

A
// 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

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
13
Q

Whats is a weak_ptr

A

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 well did you know this?
1
Not at all
2
3
4
5
Perfectly
14
Q

How to allocate an array of objects

A
// 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

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
15
Q

Ho to initialize an array of dynamically allocated objects

A

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

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
16
Q

It Is Legal to Dynamically Allocate an Empty Array

A
size_t n = get_size(); // get_size returns the number of elements needed
int* p = new int[n]; // allocate an array to hold the elements
for (int* q = p; q != p + n; ++q)
	/* process the array */ ;

Calling new[n] with n equal to 0 is legal even though we cannot create an array variable of size 0

char arr[0]; // error: cannot define a zero-length array
char *cp = new char[0]; // ok: but cp can’t be dereferenced

17
Q

How to free dynamic arrays?

A

delete p; // p must point to a dynamically allocated object or be null
delete [] pa; // pa must point to a dynamically allocated array or be null

The second statement destroys the elements in the array to which pa points and frees the corresponding memory. Elements in an array are destroyed in reverse order. That is, the last element is destroyed first, then the second to last, and so on. When we delete a pointer to an array, the empty bracket pair is essential: It indicates to the compiler that the pointer addresses the first element of an array of objects. If we omit the brackets when we delete a pointer to an array (or provide them when we delete a pointer to an object), the behavior is undefined.

18
Q

How to use dynamic arrays with smart pointers

A

The library provides a version of unique_ptr that can manage arrays allocated by new.

// up points to an array of ten uninitialized ints
unique_ptr up(new int[10]);
up.release(); // automatically uses delete[] to destroy its pointer

When a unique_ptr points to an array, we cannot use the dot and arrow member access operators. After all, the unqiue_ptr points to an array, not an object so these operators would be meaningless. On the other hand, when a unqiue_ptr points to an array, we can use the subscript operator to access the elements in the array:

for (size_t i = 0; i != 10; ++i)
up[i] = i; // assign a new value to each of the elements

Unlike unique_ptr, shared_ptrs provide no direct support for managing a dynamic array. If we want to use a shared_ptr to manage a dynamic array, we must provide our own 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

The fact that shared_ptr does not directly support managing arrays affects how we access the elements in 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
19
Q

what are the std::allocator class function ?

A

allocator 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) Deallocates memory that held n objects of type T starting at the address in the T. pointer p; p must be a pointer previously returned by allocate, and n must be the size requested when p was created. The user must run destroy on any objects that were constructed in this memory before calling deallocate.
a. construct(p, args) p must be a pointer to type T that points to raw memory; args are passed to a constructor for type T, which is used to construct an object in the memory pointed to by p.
a. destroy(p) Runs the destructor on the object pointed to by the T. pointer p.

20
Q

How to use std::allocator class

A
allocator alloc; // object that can allocate strings
auto const p = alloc.allocate(n); // allocate n unconstructed strings

The memory an allocator allocates is unconstructed. We use this memory by constructing objects in that memory. In the new library the construct member takes a pointer and zero or more additional arguments; it constructs an element at the given location. The additional arguments are used to initialize the object being constructed.

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!

In earlier versions of the library, construct took only two arguments: the pointer at which to construct an object and a value of the element type. As a result, we could only copy an element into unconstructed space, we could not use any other constructor for the element type.

cout &laquo_space;*p &laquo_space;endl; // ok: uses the string output operator
cout &laquo_space;*q &laquo_space;endl; // disaster: q points to unconstructed memory!

We must construct objects in order to use memory returned by allocate. Using unconstructed memory in other ways is undefined.

We must destroy the elements by calling destroy on each constructed element.

while (q != p)
alloc.destroy(–q); // free the strings we actually allocated

We may destroy only elements that are actually constructed.

Once the elements have been destroyed, we can either reuse the memory to hold other strings or return the memory to the system. We free the memory by calling deallocate:

alloc.deallocate(p, n);

The pointer we pass to deallocate cannot be null; it must point to memory allocated by allocate. Moreover, the size argument passed to deallocate must be the same size as used in the call to allocate that obtained the memory to which the pointer points.

21
Q

Explain std::allocator algorithms

A

uninitialized_copy(b, e, b2) Copies elements from the input range denoted by iterators b and e into unconstructed, raw memory denoted by the iterator b2. The memory denoted by b2 must be large enough to hold a copy of the elements in the input range.

uninitialized_copy_n(b, n, b2) Copies n elements starting from the one denoted by the iterator b into now memory starting at b2.

uninitialized_fill(b, e, t) Constructs objects in the range of raw memory denoted by iterators b and e as a copy of t.

uninitialized_fill_n(b, n, t) Constructs an unsigned number n objects starting at b. b must denote unconstructed, raw memory large enough to hold the given number of objects.

22
Q

How to use std::allocator algorithms

A
// 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);

Like the copy algorithm, uninitialized_copy takes three iterators. The first two denote an input sequence and the third denotes the destination into which those elements will be copied. The destination iterator passed to uninitialized_copy must denote unconstructed memory. Unlike copy, uninitialized_copy constructs elements in its destination. Like copy, uninitialized_copy returns its (incremented) destination iterator. Thus, a call to uninitialized_copy returns a pointer positioned one element past the last constructed element. In this example, we store that pointer in q, which we pass to uninitialized_fill_n. This function, like fill_n, takes a pointer to a destination, a count, and a value. It will construct the given number of objects from the given value at locations starting at the given destination.