Is it wrong to dereference a pointer to get a reference?
Solution 1
Ensure that the pointer is not NULL before you try to convert the pointer to a reference, and that the object will remain in scope as long as your reference does (or remain allocated, in reference to the heap), and you'll be okay, and morally clean :)
Solution 2
Initialising a reference with a dereferenced pointer is absolutely fine, nothing wrong with it whatsoever. If p
is a pointer, and if dereferencing it is valid (so it's not null, for instance), then *p
is the object it points to. You can bind a reference to that object just like you bind a reference to any object. Obviously, you must make sure the reference doesn't outlive the object (like any reference).
So for example, suppose that I am passed a pointer to an array of objects. It could just as well be an iterator pair, or a vector of objects, or a map
of objects, but I'll use an array for simplicity. Each object has a function, order
, returning an integer. I am to call the bar
function once on each object, in order of increasing order
value:
void bar(Foo &f) {
// does something
}
bool by_order(Foo *lhs, Foo *rhs) {
return lhs->order() < rhs->order();
}
void call_bar_in_order(Foo *array, int count) {
std::vector<Foo*> vec(count); // vector of pointers
for (int i = 0; i < count; ++i) vec[i] = &(array[i]);
std::sort(vec.begin(), vec.end(), by_order);
for (int i = 0; i < count; ++i) bar(*vec[i]);
}
The reference that my example has initialized is a function parameter rather than a variable directly, but I could just have validly done:
for (int i = 0; i < count; ++i) {
Foo &f = *vec[i];
bar(f);
}
Obviously a vector<Foo>
would be incorrect, since then I would be calling bar
on a copy of each object in order, not on each object in order. bar
takes a non-const reference, so quite aside from performance or anything else, that clearly would be wrong if bar
modifies the input.
A vector of smart pointers, or a boost pointer vector, would also be wrong, since I don't own the objects in the array and certainly must not free them. Sorting the original array might also be disallowed, or for that matter impossible if it's a map
rather than an array.
Solution 3
No. How else could you implement operator=
? You have to dereference this
in order to return a reference to yourself.
Note though that I'd still store the items in the STL container by value -- unless your object is huge, overhead of heap allocations is going to mean you're using more storage, and are less efficient, than you would be if you just stored the item by value.
Solution 4
My answer doesn't directly address your initial concern, but it appears you encounter this problem because you have an STL container that stores pointer types.
Boost provides the ptr_container library to address these types of situations. For instance, a ptr_vector
internally stores pointers to types, but returns references through its interface. Note that this implies that the container owns the pointer to the instance and will manage its deletion.
Here is a quick example to demonstrate this notion.
#include <string>
#include <boost/ptr_container/ptr_vector.hpp>
void foo()
{
boost::ptr_vector<std::string> strings;
strings.push_back(new std::string("hello world!"));
strings.push_back(new std::string());
const std::string& helloWorld(strings[0]);
std::string& empty(strings[1]);
}
Solution 5
I'd much prefer to use references everywhere but the moment you use an STL container you have to use pointers unless you really want to pass complex types by value.
Just to be clear: STL containers were designed to support certain semantics ("value semantics"), such as "items in the container can be copied around." Since references aren't rebindable, they don't support value semantics (i.e., try creating a std::vector<int&>
or std::list<double&>
). You are correct that you cannot put references in STL containers.
Generally, if you're using references instead of plain objects you're either using base classes and want to avoid slicing, or you're trying to avoid copying. And, yes, this means that if you want to store the items in an STL container, then you're going to need to use pointers to avoid slicing and/or copying.
And, yes, the following is legit (although in this case, not very useful):
#include <iostream>
#include <vector>
// note signature, inside this function, i is an int&
// normally I would pass a const reference, but you can't add
// a "const* int" to a "std::vector<int*>"
void add_to_vector(std::vector<int*>& v, int& i)
{
v.push_back(&i);
}
int main()
{
int x = 5;
std::vector<int*> pointers_to_ints;
// x is passed by reference
// NOTE: this line could have simply been "pointers_to_ints.push_back(&x)"
// I simply wanted to demonstrate (in the body of add_to_vector) that
// taking the address of a reference returns the address of the object the
// reference refers to.
add_to_vector(pointers_to_ints, x);
// get the pointer to x out of the container
int* pointer_to_x = pointers_to_ints[0];
// dereference the pointer and initialize a reference with it
int& ref_to_x = *pointer_to_x;
// use the reference to change the original value (in this case, to change x)
ref_to_x = 42;
// show that x changed
std::cout << x << '\n';
}
Oh, and you don't know if the objects were dynamically created or not.
That's not important. In the above sample, x
is on the stack and we store a pointer to x
in the pointers_to_vectors
. Sure, pointers_to_vectors
uses a dynamically-allocated array internally (and delete[]
s that array when the vector
goes out of scope), but that array holds the pointers, not the pointed-to things. When pointers_to_ints
falls out of scope, the internal int*[]
is delete[]
-ed, but the int*
s are not delete
d.
This, in fact, makes using pointers with STL containers hard, because the STL containers won't manage the lifetime of the pointed-to objects. You may want to look at Boost's pointer containers library. Otherwise, you'll either (1) want to use STL containers of smart pointers (like boost:shared_ptr
which is legal for STL containers) or (2) manage the lifetime of the pointed-to objects some other way. You may already be doing (2).
Comments
-
Mr. Boy about 2 years
I'd much prefer to use references everywhere but the moment you use an STL container you have to use pointers unless you really want to pass complex types by value. And I feel dirty converting back to a reference, it just seems wrong.
Is it?
To clarify...
MyType *pObj = ... MyType &obj = *pObj;
Isn't this 'dirty', since you can (even if only in theory since you'd check it first) dereference a NULL pointer?
EDIT: Oh, and you don't know if the objects were dynamically created or not.
-
Billy ONeal almost 14 yearsHow can a dynamically allocated object be in scope?
-
Billy ONeal almost 14 years
unique_ptr
is only available in C++0x, which can be prohibitive. -
Steven Sudit almost 14 yearsNull references are (inconveniently) undefined.
-
Steven Sudit almost 14 years@Billy: We don't know that they're dynamically allocated, just that we're pointing at them.
-
Steven Sudit almost 14 years@Billy: "or whatever similar type is appropriate". We have
auto_ptr
right now, but Boost offers a few better alternatives. -
Billy ONeal almost 14 years@Steven: Actually we do. The OP explicitly referred to objects inside of STL containers.
-
Billy ONeal almost 14 years@Steven:
auto_ptr
cannot be stored inside STL containers. -
Steven Sudit almost 14 years@Billy: Re-read what they wrote. They're using STL containers but they don't want to store it by value in the container, necessitating a copy constructor on insertion. They want a container of (smart) pointers to values that may be dynamically allocated, or may not be (such as a static array, for example).
-
Steven Sudit almost 14 years@Billy: There are many problems with
auto_ptr
, which is why I recommended alternatives while mentioning that the standard smart pointer currently available is justauto_ptr
. -
James McNellis almost 14 years
-
Steven Sudit almost 14 years@Billy: You're probably right about the overhead. Having said that, there are occasionally objects that cannot be copied.
-
Steven Sudit almost 14 years@James: Thanks. @John: If you don't know whether they're dynamically allocated, then you can't have the STL container own them. This may be ok, though, as you can just use raw pointers.
-
Mr. Boy almost 14 years@Steven: of course you can. A 3rd-party library might have methods that involve containers of pointers, and doesn't tell you where the stored objects come from.
-
Steven Sudit almost 14 years@John: You're going to have to explain what you mean.
-
A. Levy almost 14 yearsWouldn't the overhead depend on what type of container you are using? For instance, a
std::vector
reserves memory in chunks and doesn't do a separate allocation for every element you add to it. Astd::set
orstd::map
implementation could very well perform a separate allocation for each element, and thus incur the overhead you speak of. Or perhaps there is something else I'm not considering. Please elaborate. -
Steve Jessop almost 14 years@Steven: I'm pretty sure he's saying that the vector does not own the objects, and is not responsible for freeing them. This seems to have provoked a great deal of disbelief, but it does happen.
-
Steven Sudit almost 14 years@John: Even if the library only gives you pointers, it's also going to give you a function that lets you pass that pointer in to be deallocated. If this is the case, then you need to make your own
unique_ptr
variant which replaces thedelete
with a call to that function. -
Steven Sudit almost 14 years@Steve: It does happen, indeed, but I think it can be seen as a special case that's not very different.
-
Steven Sudit almost 14 yearsRight, sometimes you just want a raw pointer, allowing ownership to be dealt with elsewhere.
-
Steven Sudit almost 14 years@A. Levy: A
vector
will reallocate as needed, copying instances from the old buffer to the new. But, yes, it'll allocate a contiguous range and use placementnew
to instantiate copies in these locations. A map will likely need a single block for each node, but then again, it's not likely to ever copy a node. -
Steve Jessop almost 14 yearsAnd if the scope of the vector is safely inside some function where the referands are all valid, nobody gets hurt...
-
Billy ONeal almost 14 years@Steven: You cannot implement your own
unique_ptr
, because it relies on move semantics, which were introduced in C++0x. In which case you might as well usestd::unique_ptr
in any case. -
Billy ONeal almost 14 years@Steven: Unless you copy the map itself.
-
Billy ONeal almost 14 years@Steven: If it's in a smart pointer owned by an STL container, you can be assured it's dynamically allocated as well. Smart pointers don't own stack allocated objects.
-
gnud almost 14 yearsSmart pointers can have null destructors. Which is a good thing if you have a vector of smart pointers to objects, but want to store a stack allocated object for some reason.
-
Steven Sudit almost 14 years@Bill: That's true. I was limiting the scope to copies after the initial insertion as a result of other insertions and deletions.
-
Billy ONeal almost 14 years@Steven: Re: First comment: Yes. If the object cannot be copied, by all means store it as a pointer. Plenty of cases where it does make sense to copy though.
-
Steven Sudit almost 14 years@Bill: I think gnud's example is fair, but you're generally correct. Just tread the first parentheses as showing an optional trait.
-
Steven Sudit almost 14 yearsNice. The smart pointer is effectively built in, but the container offers reference semantics.
-
Steven Sudit almost 14 years@Bill: Please take that as a "ferinstance". There are many perfectly good smart pointers available in old C++ if you use Boost. Here's a nice summary: codesynthesis.com/~boris/blog/2010/05/24/…
-
Steven Sudit almost 14 years@Bill: Good thing copying is the default behavior.
-
Steven Sudit almost 14 yearsHmm, does the C++ standard require references to be implemented as direct pointers?
-
Max Lybbert almost 14 years"In scope" is probably not the correct term. I suspect Merlyn meant "the object will remain valid/allocated ..."
-
Merlyn Morgan-Graham almost 14 years@Billy, Max: I didn't mean syntactical scope, I meant logical scope. If the object exists, whether on the stack or on the heap, it is in scope. You could argue that a leaked object is no longer in any form of scope...
-
Billy ONeal almost 14 years@Merlyn: Ah. Note that scope refers specifically to a set of braces {}, hence the confusion ;) Perhaps change "in scope" to "valid"?
-
Merlyn Morgan-Graham almost 14 years@Billy: Okay, fine :) Now mentions the heap
-
Matthieu M. almost 14 years@A Levy: the
vector
reallocates, that's why you preferably use adeque
whenever you don't need contiguity (for C-API compatibility). -
Steven Sudit almost 14 years@Merl: Scope usually refers to visibility, whereas we're both talking about lifespan.
-
IanH almost 14 yearsNo, but most compiler usually do so. So often derefencering a 0-Pointer and assigning it to a reference is possible in practice and may lead to strange crashes at other locations. I update my answer to state this more clearly.
-
Mr. Boy almost 14 yearsIt also means you class has to have an empty/default ctor. That can mean writing extra code just to allow you to put them in containers, when an object in this state is invalid. Just seems messy.
-
Mr. Boy almost 14 years@Steven... say
MyBigClass
contains a member of typeMyMediumClass
. Now I can in theory be passed avector<MyMediumClass *>
built from the members of everyMyBigClass
-
Max Lybbert almost 14 yearsI've used scope in both senses (visibility and lifetime). But it looked like it was causing confusion in this case.
-
Steven Sudit almost 14 years@John: My parser can't decode that sentence. Are you asking about a vector of pointers to objects contained within larger objects? If so, there's nothing tricky about it. So long as the lifespan of the larger objects exceeds that of the vector, it'll work fine.