I might be rather late to the party, yet having read this just now i noticed some errors in the explanations and examples which could not be left uncommented and I thought i could point out some pitfalls, too.
The second example, at least imho, does not constitute a meaningful use of pointers at all:
Light-Dark wrote:
Code: Select all
int add1(int number)
{
return number+1;
}
int var1 = 0;
...
var1 = add1(var1);
[...]
Code: Select all
int var1 = 0;
void add1(const int* ptr)
{
*ptr = *ptr+1;
}
...
add1(&var1);
Both examples basically do the same thing, the key difference is that the second example that involves pointers does it A lot more efficiently.
The real key difference between them is that the first compiles and runs, whereas the second does not, which illustrates one of the major flaws of dealing with raw pointers directly: it can be tricky.
This
does not declare ptr to be a constant pointer, but a pointer to const, meaning the value pointed to can not be changed through it, which leads to a compiler error when this line
tries to assign to it.
I furthermore highly doubt the claim of efficiency, especially when the pointed to value is of type int, for on most systems the effort of copying a pointer is at least as costly as copying an integer and the extra indirection is most certainly more expensive than the (propably rvo-optimized away) second copy. In addition, the pointer access does prevent certain compiler optimizations, because it has to expect aliasing.
You do have somewhat of a point when claiming its worth the effort for bigger objects. I would, however, like to stress the fact that references are another and (in most cases) less error prone option for those cases:
Code: Select all
void do_something(big_object &obj)
{
expensive_operation(obj);
another_expensive_operation(obj);
}
Whilst a reference has to be initialized with an existing object, the pointer version does allow user code to pass in addresses pointing to non-existing, no longer existing or invalid objects(for instance a null pointer). In some use cases this might be desired, yet if there is no explicit need for it i would suggest using references(or well designed value types with move semantics, which could replace pointers in most of those use cases(and deal with everything neeeded(maybe pointers) internally without affecting and complicating user code). See also this excellent article:
http://cpp-next.com/archive/2009/08/wan ... -by-value/)
One use case for which pointers really are indispensable is dealing with dynamic memory directly, for there is simply no other way to handle an address returned from new or malloc/calloc/realloc/or_whatever_c_functions_eluded_me_in_my_sleep_deprived_state. In this case all bets are off and you will have to use a pointer and excercise extra caution, because you are now forced to deal with ownership issues. Every resource, once aquired, must be released again, exactly once. To use the example already present in this thread:
Code: Select all
// Generic texture allocator function
void Make_Texture_Blah(const char* fn,void* ptr,const int imagew,const int imageh); //with this signature, the function takes a copy of the pointer, therefore will not change the one passed in and the texture is lost ;_;. It should take either a void** or a void*& or simply return the pointer
// Generic sprite struct
typedef struct
{
void* texture;
int x,y;
}Sprite;
Sprite Twins[2];
...
Make_Texture_Blah("FILENAME.whatever",Twins[0].texture,256,256); // Allocate the texture, then set the pointer "texture" to point to it
Twins[1].texture = Twins[0].texture; // They now point to the same texture
In this code, the call to Make_Texture_Blah allocates(aquires) a new texture and stores its address(the only available handle to this portion of memory) in the texture pointer of Twins[0]. Since Twins[1] is supposed to use the same, unchanged texture it is reasonable to assign its pointer to the same address. This avoids the propably significant waste of memory a copy would incur, yet not without a price: it intruduces extra complexity because it is no longer clear which object(Twins[0] or Twins[1] or something completly different) "owns" the real texture, which object is both responsible and authorized to delete(release) it.
There are now 2 different handles to the same texture, and as long as one of them is still in use, the other may not delete it. In this example, if we were talking about C code, this would not be problem at all because Twins[0] and Twins[1] have (almost )the same lifetime and you could simply call a Free_Texture_Blah(void*) function before leaving the current scope, however, C++ does not allow your life to be that easy, assume this:
Code: Select all
Sprite Twins[2];
Make_Texture_Blah("FILENAME.whatever",Twins[0].texture,256,256); // Allocate the texture, then set the pointer "texture" to point to it
Twins[1].texture = Twins[0].texture; // They now point to the same texture
...
some_unknown_cpp_fun(); //maybe from a closed source library
...
Free_Texture_Blah(Twins[0]);
For all we know, some_unknown_cpp_fun could be this:
Code: Select all
void some_unknown_cpp_fun()
{
throw std::runtime_error("got you");
}
that is, it might throw an exception and prevent the code from ever reaching the Free_Texture_Blah call. One could try avoiding this by wrapping all unknown code in try{}catch(...) blocks and do the necessary cleanup following the catch, which would overly complicate everything and lead to lots of code duplication, which is why the usual way of dealing with this is utilizing the fact that destructors of completly constructed objects are guaranteed to run, even in the face of exceptions, the RAII Idiom(
http://www.stroustrup.com/bs_faq2.html#finally). In short, this idiom binds every resource to an object, allocating it in the constructor and releasing it in the destructor. For instance the code could be changed to:
Code: Select all
typedef struct
{
void* texture;
int x,y;
}Sprite;
struct Texture
{
void *ptr;
Texture(const char *fn, int w, int h)
{
Make_Texture_Blah(fn,ptr,w,h); // Allocate the texture, then set the pointer "ptr" to point to it
}
~Texture()
{
Free_Texture_Blah(ptr);
}
}
...
Texture tex("FILENAME.whatever",256,256);
Sprite Twins[2];
Twins[0].texture = tex.ptr;
Twins[1].texture = tex.ptr;
// They now point to the same texture
This solves the immediate problem described above, yet has some of its own: It wont work if it is copied, assigned to, its pointer passed to some object surviving the tex Object itself etc. etc.., which is why some clever people defined the Rule of Three(
http://en.wikipedia.org/wiki/Rule_of_th ... ramming%29), which had to be extended to the Rule of Five due to move semantics. Sounds complicated? Thats propably because it is.
Fortunately this is such a common use that "smart pointers" were intruduced in C++11, classes that work almost exactly like normal pointers, only they deal with ownership issues internally: std::unique_ptr, std::shared_ptr and std::weak_ptr. I highly recommend reading up on those.
(See also:
http://flamingdangerzone.com/cxx11/2012 ... -zero.html,
http://akrzemi1.wordpress.com/2013/07/1 ... t-feature/,
http://isocpp.org/blog/2013/07/cs-best- ... krzemieski)
Granted, this is less of a problem with the pointer itself (but rather of dynamic resource management), yet the question was regarding the purpose/usage of pointers and considering that dynamic memory management is one of the major uses i deemed it worth mentioning.
I am not claiming (raw) pointers do not have their uses(i did not mention runtime polymorphism for instance), yet the number of those is rather small and diminishing. In many places where (raw) pointers were traditionally the best option in C code, modern C++ offers better(read: safer and at least as efficient) alternatives worth considering(and teaching to newbies, who are much more likely to run into errors and frustration when using pointers they could have avoided).
I am sorry for the wall of text, hope i managed to avoid offending anyone and would also recommend a reading of this article:
http://meetingcpp.com/index.php/br/item ... inter.html