Is it wrong to dereference a pointer to get a reference?

15,546

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 deleted.

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).

Share:
15,546
Mr. Boy
Author by

Mr. Boy

SOreadytohelp

Updated on June 19, 2022

Comments

  • Mr. Boy
    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
    Billy ONeal almost 14 years
    How can a dynamically allocated object be in scope?
  • Billy ONeal
    Billy ONeal almost 14 years
    unique_ptr is only available in C++0x, which can be prohibitive.
  • Steven Sudit
    Steven Sudit almost 14 years
    Null references are (inconveniently) undefined.
  • Steven Sudit
    Steven Sudit almost 14 years
    @Billy: We don't know that they're dynamically allocated, just that we're pointing at them.
  • Steven Sudit
    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
    Billy ONeal almost 14 years
    @Steven: Actually we do. The OP explicitly referred to objects inside of STL containers.
  • Billy ONeal
    Billy ONeal almost 14 years
    @Steven: auto_ptr cannot be stored inside STL containers.
  • Steven Sudit
    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
    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 just auto_ptr.
  • James McNellis
    James McNellis almost 14 years
  • Steven Sudit
    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
    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
    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
    Steven Sudit almost 14 years
    @John: You're going to have to explain what you mean.
  • A. Levy
    A. Levy almost 14 years
    Wouldn'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. A std::set or std::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
    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
    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 the delete with a call to that function.
  • Steven Sudit
    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
    Steven Sudit almost 14 years
    Right, sometimes you just want a raw pointer, allowing ownership to be dealt with elsewhere.
  • Steven Sudit
    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 placement new 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
    Steve Jessop almost 14 years
    And if the scope of the vector is safely inside some function where the referands are all valid, nobody gets hurt...
  • Billy ONeal
    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 use std::unique_ptr in any case.
  • Billy ONeal
    Billy ONeal almost 14 years
    @Steven: Unless you copy the map itself.
  • Billy ONeal
    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
    gnud almost 14 years
    Smart 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
    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
    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
    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
    Steven Sudit almost 14 years
    Nice. The smart pointer is effectively built in, but the container offers reference semantics.
  • Steven Sudit
    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
    Steven Sudit almost 14 years
    @Bill: Good thing copying is the default behavior.
  • Steven Sudit
    Steven Sudit almost 14 years
    Hmm, does the C++ standard require references to be implemented as direct pointers?
  • Max Lybbert
    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
    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
    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
    Merlyn Morgan-Graham almost 14 years
    @Billy: Okay, fine :) Now mentions the heap
  • Matthieu M.
    Matthieu M. almost 14 years
    @A Levy: the vector reallocates, that's why you preferably use a deque whenever you don't need contiguity (for C-API compatibility).
  • Steven Sudit
    Steven Sudit almost 14 years
    @Merl: Scope usually refers to visibility, whereas we're both talking about lifespan.
  • IanH
    IanH almost 14 years
    No, 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
    Mr. Boy almost 14 years
    It 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
    Mr. Boy almost 14 years
    @Steven... say MyBigClass contains a member of type MyMediumClass. Now I can in theory be passed a vector<MyMediumClass *> built from the members of every MyBigClass
  • Max Lybbert
    Max Lybbert almost 14 years
    I've used scope in both senses (visibility and lifetime). But it looked like it was causing confusion in this case.
  • Steven Sudit
    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.