Reference and pointer C++

References vs Pointers
Both references and pointers can be used to change local variables of one function inside another function. Both of them can also be used to save copying of big objects when passed as arguments to functions or returned from functions, to get efficiency gain.
Despite above similarities, there are following differences between references and pointers.

A pointer can be declared as void but a reference can never be void
For example

int a = 10;
void* aa = &a;. //it is valid
void &ar = a; // it is not valid


References are less powerful than pointers

1) Once a reference is created, it cannot be later made to reference another object; it cannot be reseated. This is often done with pointers.
2) References cannot be NULL. Pointers are often made NULL to indicate that they are not pointing to any valid thing.
3) A reference must be initialized when declared. There is no such restriction with pointers

Due to the above limitations, references in C++ cannot be used for implementing data structures like Linked List, Tree, etc. In Java, references don’t have above restrictions, and can be used to implement all data structures. References being more powerful in Java, is the main reason Java doesn’t need pointers.

References are safer and easier to use:
1) Safer: Since references must be initialized, wild references like wild pointers are unlikely to exist. It is still possible to have references that don’t refer to a valid location (See questions 5 and 6 in the below exercise )
2) Easier to use: References don’t need dereferencing operator to access the value. They can be used like normal variables. ‘&’ operator is needed only at the time of declaration. Also, members of an object reference can be accessed with dot operator (‘.’), unlike pointers where arrow operator (->) is needed to access members.

Together with the above reasons, there are few places like copy constructor argument where pointer cannot be used. Reference must be used pass the argument in copy constructor. Similarly references must be used for overloading some operators like ++.

Difference between const char *p, char * const p and const char * const p

Prerequisite: Pointers
There is a lot of confusion when char, const, *, p are all used in different permutaions and meanings change according to which is placed where. Following article focus on differentiation and usage of all of these.

The qualifier const can be applied to the declaration of any variable to specify that its value will not be changed. const keyword applies to whatever is immediately to its left. If there is nothing to its left, it applies to whatever is immediately to its right.

    1. const char *ptr : This is a pointer to a constant character. You cannot change the value pointed by ptr, but you can change the pointer itself. “const char *” is a (non-const) pointer to a const char.  

 

// C program to illustrate 
// char const *p 
#include<stdio.h> 
#include<stdlib.h> 

int main() 
{ 
	char a ='A', b ='B'; 
	const char *ptr = &a; 
	
	//*ptr = b; illegal statement (assignment of read-only location *ptr) 
	
	// ptr can be changed 
	printf( "value pointed to by ptr: %c\n", *ptr); 
	ptr = &b; 
	printf( "value pointed to by ptr: %c\n", *ptr); 
} 

  

Output:

value pointed to by ptr:A
value pointed to by ptr:B

NOTE: There is no difference between const char *p and char const *p as both are pointer to a const char and position of ‘*'(asterik) is also same.

2\

char *const ptr : This is a constant pointer to non-constant character. You cannot change the pointer p, but can change the value pointed by ptr.

 

// C program to illustrate 
// char* const p 
#include<stdio.h> 
#include<stdlib.h> 

int main() 
{ 
	char a ='A', b ='B'; 
	char *const ptr = &a; 
	printf( "Value pointed to by ptr: %c\n", *ptr); 
	printf( "Address ptr is pointing to: %d\n\n", ptr); 

	//ptr = &b; illegal statement (assignment of read-only variable ptr) 

	// changing the value at the address ptr is pointing to 
	*ptr = b; 
	printf( "Value pointed to by ptr: %c\n", *ptr); 
	printf( "Address ptr is pointing to: %d\n", ptr); 
} 

  

 

    1. Output:

      Value pointed to by ptr: A
      Address ptr is pointing to: -1443150762
      
      Value pointed to by ptr: B
      Address ptr is pointing to: -1443150762
      

      NOTE: Pointer always points to same address, only the value at the location is changed.

    2. const char * const ptr : This is a constant pointer to constant character. You can neither change the value pointed by ptr nor the pointer ptr. 
// C program to illustrate 
//const char * const ptr 
#include<stdio.h> 
#include<stdlib.h> 

int main() 
{ 
	char a ='A', b ='B'; 
	const char *const ptr = &a; 
	
	printf( "Value pointed to by ptr: %c\n", *ptr); 
	printf( "Address ptr is pointing to: %d\n\n", ptr); 

	// ptr = &b; illegal statement (assignment of read-only variable ptr) 
	// *ptr = b; illegal statement (assignment of read-only location *ptr) 

} 

  

Output:

Value pointed to by ptr: A
Address ptr is pointing to: -255095482

NOTE: char const * const ptr is same as const char *const ptr.

 

Pointers vs References in C++

C and C++ support pointers which is different from most of the other programming languages. Other languages including C++, Java, Python, Ruby, Perl and PHP support references.

On the surface, both references and pointers are very similar, both are used to have one variable provide access to another. With both providing lots of same capabilities, it’s often unclear what is different between these different mechanisms. In this article, I will try to illustrate the differences between pointers and references.

Pointers: A pointer is a variable that holds memory address of another variable. A pointer needs to be dereferenced with *operator to access the memory location it points to.

References : A reference variable is an alias, that is, another name for an already existing variable. A reference, like a pointer is also implemented by storing the address of an object.
A reference can be thought of as a constant pointer (not to be confused with a pointer to a constant value!) with automatic indirection, i.e the compiler will apply the operator for you.

int i = 3; 

// A pointer to variable i (or stores
// address of i)
int *ptr = &i; 

// A reference (or alias) for i.
int &ref = i; 

Differences :

  1. Initalisation: A pointer can be initalised in this way:
     int a = 10;        
      int *p = &a;    
             OR 
         int *p;
       p = &a;
    we can declare and initalise pointer at same step or in multiple line.
    

    While in refrences,

    int a=10;
    int &p=a;  //it is correct
       but
    int &p;
     p=a;    // it is incorrect as we should declare and initialise references at single step.
    

    NOTE: This differences may vary from compiler to compiler.Above differences is with respect to turbo IDE.

Reassignment: A pointer can be re-assigned. This property is useful for implementation of data structures like linked list, tree, etc. See the following examples:

int a = 5;
int b = 6;
int *p;
p =  &a;
p = &b;

On the other hand, a reference cannot be re-assigned, and must be assigned at initialization.

int a = 5;
int b = 6;
int &p = a;
int &p = b;  //At this line it will show error as "multiple declaration is not allowed".

However it is valid statement,
int &q=p;

  1. Memory Address: A pointer has its own memory address and size on the stack whereas a reference shares the same memory address (with the original variable) but also takes up some space on the stack.
    NOTE However if we want true address of reference, then it can be found out in turbo IDE by writting the following statement,
       int &p = a;
       cout << &(&p);
    
  2. NULL value: Pointer can be assigned NULL directly, whereas reference cannot. The constraints associated with references (no NULL, no reassignment) ensure that the underlying operations do not run into exception situation.
  3. Indirection: You can have pointers to pointers offering extra levels of indirection. Whereas references only offer one level of indirection.I.e,
    In Pointers,
    int a = 10;
    int *p;
    int **q;  //it is valid.
    p = &a;
    q = &p;
    
    Whereas in refrences,
    
    int &p = a;
    int &&q = p; //it is reference to reference, so it is an error.
    
  4. Arithmetic operations: Various arithmetic operations can be performed on pointers whereas there is no such thing called Reference Arithmetic.(but you can take the address of an object pointed by a reference and do pointer arithmetics on it as in &obj + 5).)


Dangling, Void , Null and Wild Pointers

 

Dangling pointer

A pointer pointing to a memory location that has been deleted (or freed) is called dangling pointer. There are three different ways where Pointer acts as dangling pointer

De-allocation of memory

// Deallocating a memory pointed by ptr causes 
// dangling pointer 
#include <stdlib.h> 
#include <stdio.h> 
int main() 
{ 
	int *ptr = (int *)malloc(sizeof(int)); 

	// After below free call, ptr becomes a 
	// dangling pointer 
	free(ptr); 
	
	// No more a dangling pointer 
	ptr = NULL; 
} 

  

Function Call

// The pointer pointing to local variable becomes 
// dangling when local variable is not static. 
#include<stdio.h> 

int *fun() 
{ 
	// x is local variable and goes out of 
	// scope after an execution of fun() is 
	// over. 
	int x = 5; 

	return &x; 
} 

// Driver Code 
int main() 
{ 
	int *p = fun(); 
	fflush(stdin); 

	// p points to something which is not 
	// valid anymore 
	printf("%d", *p); 
	return 0; 
} 

  

Output:

A garbage Address

The above problem doesn’t appear (or p doesn’t become dangling) if x is a static variable.

 
// The pointer pointing to local variable doesn't 
// become dangling when local variable is static. 
#include<stdio.h> 

int *fun() 
{ 
	// x now has scope throughout the program 
	static int x = 5; 

	return &x; 
} 

int main() 
{ 
	int *p = fun(); 
	fflush(stdin); 
	
	// Not a dangling pointer as it points 
	// to static variable. 
	printf("%d",*p); 
} 

  Variable goes out of scope

void main()
{
   int *ptr;
   .....
   .....
   {
       int ch;
       ptr = &ch;
   } 
   .....   
   // Here ptr is dangling pointer
}

  

Void pointer is a specific pointer type – void * – a pointer that points to some data location in storage, which doesn’t have any specific type. Void refers to the type. Basically the type of data that it points to is can be any. If we assign address of char data type to void pointer it will become char Pointer, if int data type then int pointer and so on. Any pointer type is convertible to a void pointer hence it can point to any value.
Important Points

    1. void pointers cannot be dereferenced. It can however be done using typecasting the void pointer
    2. Pointer arithmetic is not possible on pointers of void due to lack of concrete value and thus size.
#include<stdlib.h> 

int main() 
{ 
	int x = 4; 
	float y = 5.5; 
	
	//A void pointer 
	void *ptr; 
	ptr = &x; 

	// (int*)ptr - does type casting of void 
	// *((int*)ptr) dereferences the typecasted 
	// void pointer variable. 
	printf("Integer variable is = %d", *( (int*) ptr) ); 

	// void pointer is now float 
	ptr = &y; 
	printf("\nFloat variable is= %f", *( (float*) ptr) ); 

	return 0; 
} 

  

Output:

Integer variable is = 4
Float variable is= 5.500000

Refer void pointer article for details.

 

NULL Pointer

NULL Pointer is a pointer which is pointing to nothing. In case, if we don’t have address to be assigned to a pointer, then we can simply use NULL.

#include <stdio.h> 
int main() 
{ 
	// Null Pointer 
	int *ptr = NULL; 
	
	printf("The value of ptr is %u", ptr); 
	return 0; 
} 

  

Output :

The value of ptr is 0

Important Points

  1. NULL vs Uninitialized pointer – An uninitialized pointer stores an undefined value. A null pointer stores a defined value, but one that is defined by the environment to not be a valid address for any member or object.
  2. NULL vs Void Pointer – Null pointer is a value, while void pointer is a type

Wild pointer

A pointer which has not been initialized to anything (not even NULL) is known as wild pointer. The pointer may be initialized to a non-NULL garbage value that may not be a valid address.

int main() 
{ 
	int *p; /* wild pointer */

	int x = 10; 

	// p is not a wild pointer now 
	p = &x; 

	return 0; 
} 

  

#include<iostream> 
using namespace std; 

class Test 
{ 
private: 
int x; 
int y; 
public: 
Test (int x = 0, int y = 0) { this->x = x; this->y = y; } 
Test setX(int a) { x = a; return *this; } 
Test setY(int b) { y = b; return *this; } 
void print() { cout << "x = " << x << " y = " << y << endl; } 
}; 

int main() 
{ 
Test obj1; 
obj1.setX(10).setY(20); 
obj1.print(); 
return 0; 
} 

  

‘this’ pointer in C++

To understand ‘this’ pointer, it is important to know that how objects look at functions and data members of a class.
1. Each object gets its own copy of the data member.
2. All access the same function definition as present in the code segment.
Meaning each object gets its own copy of data members and all objects share single copy of member functions.
Then now question is that if only one copy of each member function exists and is used by multiple objects, how are the proper data members are accessed and updated?
Compiler supplies an implicit pointer along with the functions names as ‘this’.
The ‘this’ pointer is passed as a hidden argument to all nonstatic member function calls and is available as a local variable within the body of all nonstatic functions. ‘this’ pointer is a constant pointer that holds the memory address of the current object. ‘this’ pointer is not available in static member functions as static member functions can be called without any object (with class name).
For a class X, the type of this pointer is ‘X* const’. Also, if a member function of X is declared as const, then the type of this pointer is ‘const X *const’ (see this GFact)

 

Smart Pointers in C++

Consider the following simple C++ code with normal pointers.

MyClass *ptr = new MyClass(); 
ptr->doSomething(); 
// We must do delete(ptr) to avoid memory leak 

  

Using smart pointers, we can make pointers to work in way that we don’t need to explicitly call delete. Smart pointer is a wrapper class over a pointer with operator like * and -> overloaded. The objects of smart pointer class look like pointer, but can do many things that a normal pointer can’t like automatic destruction (yes, we don’t have to explicitly use delete), reference counting and more.
The idea is to make a class with a pointer, destructor and overloaded operators like * and ->. Since destructor is automatically called when an object goes out of scope, the dynamically allocated memory would automatically deleted (or reference count can be decremented). Consider the following simple smartPtr class.

 
#include<iostream> 
using namespace std; 

class SmartPtr 
{ 
int *ptr; // Actual pointer 
public: 
// Constructor: Refer https://www.geeksforgeeks.org/g-fact-93/ 
// for use of explicit keyword 
explicit SmartPtr(int *p = NULL) { ptr = p; } 

// Destructor 
~SmartPtr() { delete(ptr); } 

// Overloading dereferencing operator 
int &operator *() { return *ptr; } 
}; 

int main() 
{ 
	SmartPtr ptr(new int()); 
	*ptr = 20; 
	cout << *ptr; 

	// We don't need to call delete ptr: when the object 
	// ptr goes out of scope, destructor for it is automatically 
	// called and destructor does delete ptr. 

	return 0; 
}

  Can we write one smart pointer class that works for all types?
Yes, we can use templates to write a generic smart pointer class. Following C++ code demonstrates the same.

#include<iostream> 
using namespace std; 

// A generic smart pointer class 
template <class T> 
class SmartPtr 
{ 
T *ptr; // Actual pointer 
public: 
// Constructor 
explicit SmartPtr(T *p = NULL) { ptr = p; } 

// Destructor 
~SmartPtr() { delete(ptr); } 

// Overloading dereferncing operator 
T & operator * () { return *ptr; } 

// Overloding arrow operator so that members of T can be accessed 
// like a pointer (useful if T represents a class or struct or 
// union type) 
T * operator -> () { return ptr; } 
}; 

int main() 
{ 
	SmartPtr<int> ptr(new int()); 
	*ptr = 20; 
	cout << *ptr; 
	return 0; 
} 

  

Output:

20

Smart pointers are also useful in management of resources, such as file handles or network sockets.

C++ libraries provide implementations of smart pointers in the form of auto_ptrunique_ptrshared_ptr and weak_ptr

auto_ptr, unique_ptr, shared_ptr and weak_ptr

Prerequisite – Smart Pointers
C++ libraries provide implementations of smart pointers in following types:

  • auto_ptr
  • unique_ptr
  • shared_ptr
  • weak_ptr

They all are declared in memory header file.

auto_ptr

This class template is deprecated as of C++11. unique_ptr is a new facility with a similar functionality, but with improved security.
auto_ptr is a smart pointer that manages an object obtained via new expression and deletes that object when auto_ptr itself is destroyed.
An object when described using auto_ptr class it stores a pointer to a single allocated object which ensures that when it goes out of scope, the object it points to must get automatically destroyed. It is based on exclusive ownership model i.e. two pointers of same type can’t point to the same resource at the same time. As shown in below program, copying or assigning of pointers changes the ownership i.e. source pointer has to give ownership to the destination pointer.

Image_1

// C++ program to illustrate the use of auto_ptr 
#include<iostream> 
#include<memory> 
using namespace std; 

class A 
{ 
public: 
	void show() { cout << "A::show()" << endl; } 
}; 

int main() 
{ 
	// p1 is an auto_ptr of type A 
	auto_ptr<A> p1(new A); 
	p1 -> show(); 

	// returns the memory address of p1 
	cout << p1.get() << endl; 

	// copy constructor called, this makes p1 empty. 
	auto_ptr <A> p2(p1); 
	p2 -> show(); 

	// p1 is empty now 
	cout << p1.get() << endl; 

	// p1 gets copied in p2 
	cout<< p2.get() << endl; 

	return 0; 
} 

  

Output:

A::show()
0x1b42c20
A::show()
0          
0x1b42c20

The copy constructor and the assignment operator of auto_ptr do not actually copy the stored pointer instead they transfer it, leaving the first auto_ptr object empty. This was one way to implement strict ownership, so that only one auto_ptr object can own the pointer at any given time i.e. auto_ptr should not be used where copy semantics are needed.

Why is auto_ptr deprecated?
It takes ownership of the pointer in a way that no two pointers should contain the same object. Assignment transfers ownership and resets the rvalue auto pointer to a null pointer. Thus, they can’t be used within STL containers due to the aforementioned inability to be copied.

 

unique_ptr

std::unique_ptr was developed in C++11 as a replacement for std::auto_ptr.
unique_ptr is a new facility with a similar functionality, but with improved security (no fake copy assignments), added features (deleters) and support for arrays. It is a container for raw pointers. It explicitly prevents copying of its contained pointer as would happen with normal assignment i.e. it allows exactly one owner of the underlying pointer.
So, when using unique_ptr there can only be at most one unique_ptr at any one resource and when that unique_ptr is destroyed, the resource is automatically claimed. Also, since there can only be one unique_ptr to any resource, so any attempt to make a copy of unique_ptr will cause a compile time error.

 unique_ptr<A> ptr1 (new A);

 // Error: can't copy unique_ptr
 unique_ptr<A> ptr2 = ptr1;    

But, unique_ptr can be moved using the new move semantics i.e. using std::move() function to transfer ownership of the contained pointer to another unique_ptr.

// Works, resource now stored in ptr2
unique_ptr<A> ptr2 = move(ptr1); 

So, it’s best to use unique_ptr when we want a single pointer to an object that will be reclaimed when that single pointer is destroyed.

 

// C++ program to illustrate the use of unique_ptr 
#include<iostream> 
#include<memory> 
using namespace std; 

class A 
{ 
public: 
	void show() 
	{ 
		cout<<"A::show()"<<endl; 
	} 
}; 

int main() 
{ 
	unique_ptr<A> p1 (new A); 
	p1 -> show(); 

	// returns the memory address of p1 
	cout << p1.get() << endl; 

	// transfers ownership to p2 
	unique_ptr<A> p2 = move(p1); 
	p2 -> show(); 
	cout << p1.get() << endl; 
	cout << p2.get() << endl; 

	// transfers ownership to p3 
	unique_ptr<A> p3 = move (p2); 
	p3->show(); 
	cout << p1.get() << endl; 
	cout << p2.get() << endl; 
	cout << p3.get() << endl; 

	return 0; 
} 

 

Output:

A::show()
0x1c4ac20
A::show()
0          // NULL
0x1c4ac20
A::show()
0          // NULL
0          // NULL
0x1c4ac20

The below code returns a resource and if we don’t explicitly capture the return value, the resource will be cleaned up. If we do, then we have exclusive ownership of that resource. In this way we can think of unique_ptr as safer and better replacement of auto_ptr.

unique_ptr<A> fun()
{
    unique_ptr<A> ptr(new A);

    /* ...
       ... */

    return ptr;
}

When to use unique_ptr?
Use unique_ptr when you want to have single ownership(Exclusive) of resource. Only one unique_ptr can point to one resource. Since there can be one unique_ptr for single resource its not possible to copy one unique_ptr to another.

shared_ptr

A shared_ptr is a container for raw pointers. It is a reference counting ownership model i.e. it maintains the reference count of its contained pointer in cooperation with all copies of the shared_ptr. So, the counter is incremented each time a new pointer points to the resource and decremented when destructor of object is called.

Reference Counting: It is a technique of storing the number of references, pointers or handles to a resource such as an object, block of memory, disk space or other resources.

An object referenced by the contained raw pointer will not be destroyed until reference count is greater than zero i.e. until all copies of shared_ptr have been deleted.
So, we should use shared_ptr when we want to assign one raw pointer to multiple owners.

// C++ program to demonstrate shared_ptr 
#include<iostream> 
#include<memory> 
using namespace std; 

class A 
{ 
public: 
	void show() 
	{ 
		cout<<"A::show()"<<endl; 
	} 
}; 

int main() 
{ 
	shared_ptr<A> p1 (new A); 
	cout << p1.get() << endl; 
	p1->show(); 
	shared_ptr<A> p2 (p1); 
	p2->show(); 
	cout << p1.get() << endl; 
	cout << p2.get() << endl; 

	// Returns the number of shared_ptr objects 
	//referring to the same managed object. 
	cout << p1.use_count() << endl; 
	cout << p2.use_count() << endl; 

	// Relinquishes ownership of p1 on the object 
	//and pointer becomes NULL 
	p1.reset(); 
	cout << p1.get() << endl; 
	cout << p2.use_count() << endl; 
	cout << p2.get() << endl; 

	return 0; 
} 

  

Output:

0x1c41c20
A::show()
A::show()
0x1c41c20
0x1c41c20
2
2
0          // NULL
1
0x1c41c20

When to use shared_ptr?
Use shared_ptr if you want to share ownership of resource . Many shared_ptr can point to single resource. shared_ptr maintains reference count for this propose. when all shared_ptr’s pointing to resource goes out of scope the resource is destroyed.

weak_ptr

A weak_ptr is created as a copy of shared_ptr. It provides access to an object that is owned by one or more shared_ptr instances, but does not participate in reference counting. The existence or destruction of weak_ptr has no effect on the shared_ptr or its other copies. It is required in some cases to break circular references between shared_ptr instances.


Cyclic Dependency (Problems with shared_ptr):
 Let’s consider a scenario where we have two classes A and B, both have pointers to other classes. So, it’s always be like A is pointing to B and B is pointing to A. Hence, use_count will never reach zero and they never get deleted.

 


Image_2

This is the reason we use weak pointers(weak_ptr) as they are not reference counted. So, the class in which weak_ptr is declared doesn’t have strong hold of it i.e. the ownership isn’t shared, but they can have access to these objects.

Image_3

So, in case of shared_ptr because of cyclic dependency use_count never reaches zero which is prevented using weak_ptr, which removes this problem by declaring A_ptr as weak_ptr, thus class A does not own it, only have access to it and we also need to check the validity of object as it may go out of scope. In general, it is a design issue.

When to use weak_ptr?
When you do want to refer to your object from multiple places – for those references for which it’s ok to ignore and deallocate (so they’ll just note the object is gone when you try to dereference).

 

 

转载于:https://www.cnblogs.com/EMH899/p/10805599.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值