views:

370

answers:

7

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.

+4  A: 

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.

Billy ONeal
@Billy: You're probably right about the overhead. Having said that, there are occasionally objects that cannot be copied.
Steven Sudit
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.
A. Levy
@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.
Steven Sudit
@Steven: Unless you copy the map itself.
Billy ONeal
@Bill: That's true. I was limiting the scope to copies after the initial insertion as a result of other insertions and deletions.
Steven Sudit
@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.
Billy ONeal
@Bill: Good thing copying is the default behavior.
Steven Sudit
@A Levy: the `vector` reallocates, that's why you preferably use a `deque` whenever you don't need contiguity (for C-API compatibility).
Matthieu M.
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.
John
@John: Good point.
Steven Sudit
+6  A: 

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

Merlyn Morgan-Graham
How can a dynamically allocated object be in scope?
Billy ONeal
Null references are (inconveniently) undefined.
Steven Sudit
@Billy: We don't know that they're dynamically allocated, just that we're pointing at them.
Steven Sudit
@Steven: Actually we do. The OP explicitly referred to objects inside of STL containers.
Billy ONeal
@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: 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.
Billy ONeal
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.
gnud
@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
"In scope" is probably not the correct term. I suspect Merlyn meant "the object will remain valid/allocated ..."
Max Lybbert
@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...
Merlyn Morgan-Graham
@Merlyn: Ah. Note that scope refers specifically to a set of braces {}, hence the confusion ;) Perhaps change "in scope" to "valid"?
Billy ONeal
@Billy: Okay, fine :) Now mentions the heap
Merlyn Morgan-Graham
@Merl: Scope usually refers to visibility, whereas we're both talking about lifespan.
Steven Sudit
I've used scope in both senses (visibility and lifetime). But it looked like it was causing confusion in this case.
Max Lybbert
@Merlyn: +1 for edit. :)
Billy ONeal
+1  A: 

If you want the container to actually contain objects that are dynamically allocated, you shouldn't be using raw pointers. Use unique_ptr or whatever similar type is appropriate.

Steven Sudit
`unique_ptr` is only available in C++0x, which can be prohibitive.
Billy ONeal
@Billy: "or whatever similar type is appropriate". We have `auto_ptr` right now, but Boost offers a few better alternatives.
Steven Sudit
@Steven: `auto_ptr` cannot be stored inside STL containers.
Billy ONeal
why, what happens?
John
@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`.
Steven Sudit
@John: [`auto_ptr` does not have the correct copy semantics to be placed in a container.](http://stackoverflow.com/questions/111478/why-is-it-wrong-to-use-stdauto-ptr-with-stl-containers).
James McNellis
@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.
Steven Sudit
@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.
John
@John: You're going to have to explain what you mean.
Steven Sudit
@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.
Steve Jessop
@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
@Steve: It does happen, indeed, but I think it can be seen as a special case that's not very different.
Steven Sudit
@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
@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: http://www.codesynthesis.com/~boris/blog/2010/05/24/smart-pointers-in-boost-tr1-cxx-x0/
Steven Sudit
@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`
John
@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.
Steven Sudit
+5  A: 

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.

Steve Jessop
Right, sometimes you just want a raw pointer, allowing ownership to be dealt with elsewhere.
Steven Sudit
And if the scope of the vector is safely inside some function where the referands are all valid, nobody gets hurt...
Steve Jessop
+2  A: 

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]);
}
Steve Guidi
Nice. The smart pointer is effectively built in, but the container offers reference semantics.
Steven Sudit
+1  A: 

There's nothing wrong with it, but please be aware that on machine-code level a reference is usually the same as a pointer. So, usually the pointer isn't really dereferenced (no memory access) when assigned to a reference. So in real life the reference can be 0 and the crash occurs when using the reference - what can happen much later than its assignemt.

Of course what happens exactly heavily depends on compiler version and hardware platform as well as compiler options and the exact usage of the reference.

Officially the behaviour of dereferencing a 0-Pointer is undefined and thus anything can happen. This anything includes that it may crash immediately, but also that it may crash much later or never.

So always make sure that you never assign a 0-Pointer to a reference - bugs likes this are very hard to find.

Edit: Made the "usually" italic and added paragraph about official "undefined" behaviour.

IanH
Hmm, does the C++ standard *require* references to be implemented as direct pointers?
Steven Sudit
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.
IanH
+1  A: 

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

Max Lybbert