Contents
Introduction
This article explains the reason behind using pointer-to-pointer and reference-to-pointer to modify a pointer passed to a function, so as to understand their usage better. For brevity, I use the terms, ptr-to-ptr and ref-to-ptr to represent them respectively. In this article, I'm not going to discuss how to use ptr-to-ptr as a 2 dimensional array or array of pointers. Please note we can use ptr-to-ptr in both C and C++, but we can use ref-to-ptr only in C++.
Why We Need Them?
When we use "pass by pointer" to pass a pointer to a function, only a copy of the pointer is passed to the function. We can say "pass by pointer" is passing a pointer by value. In most cases, this does not present a problem. But the problem comes when you modify the pointer inside the function. Instead of modifying the variable, you are only modifying a copy of the pointer and the original pointer remains unmodified, that is, it still points to the old variable. The code below demonstrates this behavior.
int g_n = 42;
void example_ptr()
{
int n = 23;
int* pn = &n;
std::cout << "example_ptr()" << std::endl;
std::cout << "Before :" << *pn << std::endl;
func_ptr(pn);
std::cout << "After :" << *pn << std::endl; }
void func_ptr(int* pp)
{
pp = &g_n;
}
This is not a problem with C/C++ only. This is exactly the same case with Python. In the example below where a dictionary, instruments
, is initialized again in fill_up
method. After the method call, instruments
is still empty. Because only a copy of instruments
is passed to fill_up
. You can think of instruments
as pointer which is passed by copy. The initializing code is like calling C++ new
to instantiate the dictionary. instruments
inside fill_up
points to new memory while the outer one still points to original memory. Underneath the surface, computer languages makes use of address to load and fetch data. There is no escape from using address underlyingly, even in Python, a language which does not support pointer and address. In Java, references are disguised pointers or indirect pointers, with meta information, which is dereferenced automatically, whose address is updated after garbage collection.
def fill_up(instruments):
instruments = {}
instruments["1"] = 1
instruments = {}
fill_up(instruments)
print "len(instruments):%d" % len(instruments)
Output shows instruments
has zero elements after the call.
len(instruments):0
Initialization is removed in the below code:
def fill_up(instruments):
instruments["1"] = 1
instruments = {}
fill_up(instruments)
print "len(instruments):%d" % len(instruments)
Now, output shows instruments
length correctly!
len(instruments):1
Syntax of Pointer to Pointer
This is how you call the function with ptr-to-ptr parameter.
int g_n = 42;
void example_ptr_to_ptr()
{
int n = 23;
int* pn = &n;
std::cout << "example_ptr_to_ptr()" << std::endl;
std::cout << "Before :" << *pn << std::endl;
func_ptr_to_ptr(&pn);
std::cout << "After :" << *pn << std::endl; }
void func_ptr_to_ptr(int** pp)
{
*pp = &g_n;
}
Syntax of Reference to Pointer
Now let us look at how you call the function with ref-to-ptr parameter. Many C++ developers mistook this type as pointer to reference. C++ types are inferred by reading from right to left!.
int g_n = 42;
void example_ref_to_ptr()
{
int n = 23;
int* pn = &n;
std::cout << "example_ref_to_ptr()" << std::endl;
std::cout << "Before :" << *pn << std::endl;
func_ref_to_ptr(pn);
std::cout << "After :" << *pn << std::endl; }
void func_ref_to_ptr(int*& pp)
{
pp = &g_n;
}
Syntax of Returning Pointer
Or you can just simply return the pointer.
int g_n = 42;
void example_ret_ptr()
{
int n = 23;
int* pn = &n;
std::cout << "example_ret_ptr()" << std::endl;
std::cout << "Before :" << *pn << std::endl;
pn = func_ret_ptr();
std::cout << "After :" << *pn << std::endl; }
int* func_ret_ptr()
{
return &g_n;
}
Syntax of Returning Reference
Or you can just simply return the reference.
int g_n = 42;
void example_ret_ref()
{
int n = 23;
int* pn = &n;
std::cout << "example_ret_ref()" << std::endl;
std::cout << "Before :" << *pn << std::endl;
pn = &func_ret_ref();
std::cout << "After :" << *pn << std::endl; }
int& func_ret_ref()
{
return g_n;
}
Preference of One Over the Other?
Now we have seen the syntax of ptr-to-ptr and ref-to-ptr. Are there any advantages of one over the other? I am afraid, no. The usage of one of both, for some programmers are just personal preferences. Some who use ref-to-ptr say the syntax is "cleaner" while some who use ptr-to-ptr, say ptr-to-ptr syntax makes it clearer to those reading what you are doing.
Do Not Mistake Pointer to Pointer Arguments
Do not mistake every ptr-to-ptr arguments as purely ptr-to-ptr. An example would be when some write int main(int argc, char *argv[])
as int main(int argc, char **argv)
where **argv
is actually an array of pointers. Be sure to check out the library documentation first!
Reference to Pointer type (RTTI)
You cannot use RTTI to find out the type of ref-to-ptr. As typeid()
does not support reference types.
void test(int*& rpInt)
{
std::cout << "type of *&rpInt: " << typeid(rpInt).name()
<< std::endl;
}
Conclusion
You may ask if you would ever use ptr-to-ptr and ref-to-ptr in your projects and if it is necessary to know about them. Well, as developers, we use libraries and technologies developed by others. One example would be COM uses ptr-to-ptr to return an interface pointer using CoCreateInstance()
and IUnknown::QueryInterface()
. Up to some point in your developer career, you are definitely going to come across them. It is good to know them.
History
- 2018/01/11: Add links to show C++ types are inferred by reading from right to left, so as to show
*&
is reference to pointer, not pointer to reference - 2017/08/23: Show Python code which has the same problem as C++
- 2016/10/02: Simpler examples
- 2009/04/29: Updated the explanations and added tracking reference to a handle in C++/CLI section
- 2003/09/01: First release