8

Generally it appears preferable in C++ that code that functions that take a parameter to a polymorphic object takes a pointer to it:

class Base {};
class DerivedA : public Base {};
class DerivedB : public Base {};

void OperateOnObject(Base* obj) {};

vector<Base*> objects;
objects.push_back(new DerivedA());
objects.push_back(new DerivedB());

for (size_t i=0; i<objects.size(); i++) {
  OperateOnObject(objects[i]);
}

Why is it that OperateOnObject() is usually written to take a pointer to Base rather than a reference? Are there potential problems with slicing? (eg the vtable gets lost)

I've seen this response to a similar-sounding question, but it doesn't seem to address the same issue.

Community
  • 1
  • 1
the_mandrill
  • 29,792
  • 6
  • 64
  • 93
  • 3
    In all the projects I worked for, the preference of pointer vs. reference was always depending on something else than polymophism... – PlasmaHH May 11 '12 at 10:51
  • One extra factor is that in VisualStudio you appear to get a lot more debugging information for pointers (eg type info for the most-derived type) than for references, so that's also a factor in preferring one over the other – the_mandrill May 11 '12 at 11:03
  • 1
    In a good design the (in)abilities of an IDE/debugger should not influence the basic properties of the design. – PlasmaHH May 11 '12 at 11:13
  • That's true, but all other things being equal... – the_mandrill May 11 '12 at 11:38
  • They are seldom equal, usually you have other requirements that make up for one or another direction, e.g. necessity for nullptr or design policy that says to use pointers to denote transfer of ownership etc. etc. etc. – PlasmaHH May 11 '12 at 11:56

2 Answers2

10

There's certainly no problem with slicing --- your classes don't need vtables since they have no virtual functions, but even if they did have them, pass-by-reference doesn't copy the object and hence doesn't slice anything.

You can make virtual calls via references exactly as you can via pointers. So as far as I know there's no practical reason, it's for style only.

Perhaps it's to do with the fact that polymorphic objects are generally created by pointer with new, and of course must be stored in containers by pointer (or smart pointer) since you can't have a container of references. It might then seem more consistent to manipulate them by pointer always.

Also, if you want to use OperateOnObject in a standard algorithm like for_each, then either it has to accept the element type of the container, which is a pointer, or else you have to wrap it in a function adaptor that does the dereference. Standard C++ doesn't have that adaptor, so it's a world of hassle to base your style on it.

Related: look what happens if OperateOnObject takes a reference, and you iterate your vector using iterators:

for (vector<Base*>::iterator i = objects.begin(); i != objects.end(); ++i) {
    OperateOnObject(**i);
}

The 20th time that double-indirection annoys or confuses you, you will change the signature of OperateOnObject ;-)

As an aside, some style guides warn against passing non-const references regardless of whether the type is a polymorphic base class, on the grounds you can't immediately tell the difference between pass-by-reference and pass-by-value when you're reading code at the call site. So they'd prefer OperateOnObject to take a pointer anyway.

Personally I think that argument is a little weak -- a function's name should tell you roughly what it does, and in particular a statement like ChangeTheObject(my_object); is not-so-subtly hinting to me that since it's not using any return value, it must change its argument. But I accept that if you follow a style properly, there's some benefit to clearly and consistently distinguishing mutators from pure functions.

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
9

References do not present problems with slicing - in this respect, they are as good as pointers. There is no "hard" reason to prefer pointers, unless you want semantics of NULL to be available to your code (i.e. an ability to pass "nothing", which is not there if you use references).

I think the pointer types are often used as parameters of polymorphic functions to match the semantic of collections: since you cannot make a vector of references, your code will end up having to dereference pointers obtained from collections before passing them to functions. This may be annoying.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • Ok, so really it's just a stylistic issue, there's no technical reason for it. This seems to be in agreement with Steve's response – the_mandrill May 11 '12 at 11:04