- Smart Pointers
- Passing smart pointers to functions
- Reference
- Atomic Smart Pointers
- Smart Pointers Class Member
The size of a pointer is dependent upon the architecture of the computer — a 32-bit computer uses 32-bit memory addresses — consequently, a pointer on a 32-bit machine is 32 bits (4 bytes). On a 64-bit machine, a pointer would be 64 bits (8 bytes). This is true regardless of what is being pointed to:
std::cout<<"sizeof(char *)= " << sizeof(char *)<<std::endl;
std::cout<<"sizeof(int *)= " <<sizeof(int *)<<std::endl;
std::cout<<"sizeof(void *)= " <<sizeof(void *)<<std::endl;
std::cout<<"sizeof(double *)= " <<sizeof(double *)<<std::endl;
std::cout<<"sizeof(person *)= " <<sizeof(person *)<<std::endl;
std::cout<<"sizeof(employee *)= " <<sizeof(employee *)<<std::endl;
When you want to access the data/value in the memory that the pointer points to - the contents of the address with that numerical index - then you dereference the pointer. Consider given a pointer such as p below:
const char* p = "abc";
To refer to the characters p points to, we dereference p using one of these notations:
*p == 'a'; // The first character at address p will be 'a'
p[1] == 'b'; // p[1] actually dereferences a pointer created by adding p and 1 times the size of the things to which p points, In this case they're char which are 1 byte in C...
*(p + 1) == 'b'; // Another notation for p[1]
In C, NULL
and 0
- and additionally in C++ 0x
or NULL
was replaced by nullptr
which can be used to indicate that a pointer doesn't currently hold the memory address of a variable, and shouldn't be dereferenced.
0
or NULL
could cause ambiguity in overloaded function resolution,for instance:
f(int);
f(foo *);
you can 0
to both of them. in C++ nullptr
has type std::nullptr_t
and it's implicitly convertible to any pointer type. Thus, it'll match std::nullptr_t
or pointer types in overload resolution, but not other types such as int.
wild pointer is an uninitialized pointers. It points to some random address in the memory and may cause a program to crash or behave badly.
// wild pointer, uninitialized, pointing to some unknown memory location
int *ptr;
// This will cause the program to crash or behave badly.
*ptr = 10;
to avoid this, allway define the pointer as following:
int *ptr=nullptr;
A dangling pointer is a (non-NULL) pointer which points to unallocated (already freed) memory area.
Class *object = new Class();
Class *object2 = object;
delete object;
object = nullptr;
// now object2 points to something which is not valid anymore
This can occur even in stack allocated objects:
Object *method() {
Object object;
return &object;
}
Object *object2 = method();
// obj
Refs: 1
The following are some typical causes of a segmentation fault:
- Attempting to access a nonexistent memory address (outside process's address space)
- Attempting to access memory the program does not have rights to (such as kernel structures in process context)
- Attempting to write read-only memory (such as code segment) These often happens while dereferencing or assigning null pointer/ wild pointer/ dangling pointer, heap overflow, stack overflow When you access an array index, C and C++ don't do bound checking. Segmentation faults only happen when you try to read or write to a page that was not allocated (or try to do something on a page which isn't permitted, e.g. trying to write to a read-only page), but since pages are usually pretty big (multiples of a few kilobytes), it often leaves you with lots of room to overflow.
By setting the followings flag you can find the issue:
set(CMAKE_CXX_FLAGS "-fsanitize=address ${CMAKE_CXX_FLAGS}")
set(CMAKE_CXX_FLAGS "-fno-omit-frame-pointer ${CMAKE_CXX_FLAGS}")
Smart pointers represent a way to express ownership over a resource. They prevent memory leaksing by making the memory deallocation automatic and making object destruction automatic. An object controlled by a smart pointer is automatically destroyed when the last (or only) owner of an object is destroyed. An object can solely own another object (unique_ptr), or it can share the ownership of an object with others (shared_ptr), or it does not own but only refers to an object(weak_pointer).
Sole ownership: Use cases of sole ownership include having to move around an expensive to copy resource, a resource that is owned by a single subroutine but needs to be dynamically allocated, passing a resource between threads, various implementations of singletons or pimpl and compatibility with C libraries which use pointers.
Raw pointers do not establish the unique ownership themselves. A class that uses raw pointers for unique ownership must explicitly implement the move-construction/assignment and disallow copy-construction/assignment.
unique pointer, has less overhead relative to share_ptr, so use a unique pointer instead of a shared pointer where it suffices. A unique pointer has no copy constructor can not be copied, and can only be moved. Also possible to transfer the exclusive ownership by moving. The underlying resource is destructed when it exists the scope.
There are different ways to create a unique pointer:
First way:
std::unique_ptr<person> entity=std::unique_ptr<person>(new person);
Second way(recommended) for exception safety
std::unique_ptr<person> entity=std::make_unique<person>();
This will fail because it is a unique pointer, it can not be copied:
std::unique_ptr<person> secondentity=entity;
This will work because because we have move constructor
std::unique_ptr<person> secondentity=std::move(entity);
release()
method releases the ownership of its stored pointer, return its value and replace it with a null pointer. This call will not destroy the managed object, but the unique_ptr object is released from the responsibility of deleting the object. you have to delete the object at some point.
To force the destruction of the object pointed, either use member function reset or perform an assignment operation on it.
std::unique_ptr<int> unique_pointer (new int);
int * manual_pointer;
*unique_pointer=1;
manual_pointer = unique_pointer.release();
if(unique_pointer ==nullptr )
std::cout << "unique_pointer is now empty" << '\n';
std::cout << "manual_pointer points to " << *manual_pointer << '\n';
delete manual_pointer;
Refs: 1
Shared ownership: shared pointer, It is mainly meant for multi-threaded resource sharing and gives the guarantee that the object won’t be freed by another thread. The managed object is deleted when the last owning shared_ptr is destroyed. In a typical implementation, a shared_ptr contains only two pointers:
- A raw pointer to the managed object (can be returned by calling get())
- A pointer to the control block.
Several shared_ptr instances can share the management of an object's lifetime through a common control block.
|----------------------------| |----------------------------|
| shared_ptr<>Ptr1 | | shared_ptr<>Ptr1 |
|----------------------------| |------------| |----------------------------|
|Pointer to the Object |------------------>| Object |<--------------|Pointer to the Object |
| | |------------| | |
|Pointer to the control block|----| |---|Pointer to the control block|
|----------------------------| | | |----------------------------|
| |
| |
| |
| |-----------------------------| |
| | control block | |
|--->|raw pointer to object |<---|
|Shared reference counter |
|Weak reference counter |
|Pointer to allocator, deleter|
|-----------------------------|
you can use .use_count()
to access number of references.
Refs: 1
There are several ways to create a shared pointer:
Single line with new:
std::shared_ptr<manager> ceo=std::shared_ptr<manager>(new manager);
std::shared_ptr<manager> ceo1(new manager);
Multi line:
std::shared_ptr<manager> cto;
cto.reset(new manager);
Preferred way using make_shared:
std::shared_ptr<manager> boss=std::make_shared<manager>();
When you create an object with new , and then create a shared_ptr , there are two dynamic memory allocations that happen: one for the object itself from the new, and then a second for the manager object created by the shared_ptr constructor. when you use make_shared, C++ compiler does a single memory allocation big enough to hold both the manager object and the new object.
when you erase items in a vector the occupied space will be freed, but if you have stored pointers inside a vector you have to manualy call delete function to free the space.
The destructor for std::vector<T>
ensures that the destructor for T is called for every element stored in the vector.
Refs: 1
std::weak_ptr is a very good way to solve the dangling pointer problem. By just using raw pointers it is impossible to know if the referenced
data has been deallocated or not. Instead, by letting a std::shared_ptr
manage the data, and supplying std::weak_ptr
to users of the data, the users can check validity of the data by calling expired()
or lock()
.
You could not do this with std::shared_ptr
alone, because all std::shared_ptr
instances share the ownership of the data which is not removed before all instances of std::shared_ptr
are removed. Here is an example of how to check for dangling pointer using lock()
:
// OLD, problem with dangling pointer
// PROBLEM: ref will point to undefined data!
int* ptr = new int(10);
int* ref = ptr;
delete ptr;
// NEW
// SOLUTION: check expired() or lock() to determine if pointer is valid
// empty definition
std::shared_ptr<int> sptr;
// takes ownership of pointer
sptr.reset(new int);
*sptr = 10;
// get pointer to data without taking ownership
std::weak_ptr<int> weak1 = sptr;
// deletes managed object, acquires new pointer
sptr.reset(new int);
*sptr = 5;
// get pointer to new data without taking ownership
std::weak_ptr<int> weak2 = sptr;
// weak1 is expired!
if(auto tmp = weak1.lock())
std::cout << *tmp << '\n';
else
std::cout << "weak1 is expired\n";
// weak2 points to new data (5)
if(auto tmp = weak2.lock())
std::cout << *tmp << '\n';
else
std::cout << "weak2 is expired\n";
Weak pointer, they don't increase the ref count:
std::shared_ptr<person> person0;
{
std::shared_ptr<person> sharedEntity0(new person(0));
person0=sharedEntity0;
}
std::cout<<"sharedEntity0 is still alive because of person0=sharedEntity0" <<std::endl;
if we change the above code to the following the object will die after the {} block
std::weak_ptr<person> person1;
{
std::shared_ptr<person> sharedEntity1(new person(1));
person1=sharedEntity1;
}
std::cout<<"sharedEntity1 died befor reaching here since person1 is a weak_ptr" <<std::endl;
ptr != nullptr
or
!ptr
std::shared_ptr<Base> b(new Base());
std::shared_ptr<Derived> d = std::static_pointer_cast<Derived>(b);
std::shared_ptr<person> b(new person());
std::shared_ptr<manager> d = std::static_pointer_cast<manager>(b);
Read the full here
Basically pointers can be reassigned to different location in memory but references can only stick to one variable. reference is like an alias for an existing variable, it shares the same address as the original variable. also a reference can not be null.
int foo=2;
int &ref=foo;
int *ptr=&foo;
foo++;
so the values are:
ref is: 3
foo is: 3
&ref is: 0x7ffee0953bdc
&foo is: 0x7ffee0953bdc
ptr is: 0x7ffee0953bdc
*ptr is: 3
&foo
and &ref
are the same, because reference is just like an alias for variable. Pointer is a variable that store the address of an other variable, so it can be changed but reference can not be changed. The followings will rise an error (we need rvalue ref):
int & r3 = 200;
uninitialized reference:
& ref = x;
uninitialized reference:
int &ref_2;
lvalue is an object that occupies some identifiable address in memory (heap, stack) and not in CPU register. lvalues can be written on the left of assignment operator =
. rvalue is, the simple explanation is that it doesn't have a memory address. i.e. the number 4.
Reference in C++ is an alias for an existing variable, to the already existing variable. They are declared using the &
before the name of the variable.
lvalue reference:
int x= 1;
int& y = x;
Refs: 1
std::vector and std::list elements cannot be references, because:
- references have to be initialized.
- they cannot be reassigned, so the following won't compile:
std::vector<person&> m_people{};
or
std::vector<<std::unique_ptr<person>>> people;
or
std::vector<<const person> people;
Essentially, std::reference_wrapper is a class that acts like a reference, but also allows assignment and copying, so it’s compatible with std::lists and std::vector.
std::vector<std::reference_wrapper<std::unique_ptr<person>>> people;
std::unique_ptr empolyee1= std::make_unique<person>();
people.push_back(empolyee1 );
Function templates ref and cref are helper functions that generate an object of type std::reference_wrapper
void inc(int a, int &b, const int &c) {
std::cout << "In function: " << a << ' ' << b << ' ' << c << '\n';
a++;
b++;
return;
}
int a, b, c;
a = 1;
b = 10;
c = 100;
std::cout << "before bind: " << a << ' ' << b << ' ' << c << '\n';
auto f = std::bind(inc, a, std::ref(b), std::cref(c));
a = 5;
b = 50;
c = 500;
std::cout << "before call: " << a << ' ' << b << ' ' << c << '\n';
f();
std::cout << "after call: " << a << ' ' << b << ' ' << c << '\n';
the output is:
before bind: 1 10 100
before call: 5 50 500
In function: 1 50 500
after call: 5 51 500
std::cout<< std::is_same<int, std::remove_pointer<int>::type>();
std::cout<< std::is_same<int, std::remove_reference<int &>::type>();
Refs: 1
Read the full here