11

Let's say I have an object Employee_Storage that contains a database connection data member. Should this data member be stored as a pointer or as a reference?

  • If I store it as a reference, I don't have to do any NULL checking. (Just how important is NULL checking anyway?)

  • If I store it as a pointer, it's easier to setup Employee_Storage (or MockEmployee_Storage) for the purposes of testing.

Generally, I've been in the habit of always storing my data members as references. However, this makes my mock objects difficult to set up, because instead of being able to pass in NULLs (presumably inside a default constructor) I now must pass in true/mock objects.

Is there a good rule of thumb to follow, specifically with an eye towards testability?

Runcible
  • 7,006
  • 12
  • 42
  • 62

5 Answers5

17

It's only preferable to store references as data members if they're being assigned at construction, and there is truly no reason to ever change them. Since references cannot be reassigned, they are very limited.

In general, I typically store as pointers (or some form of templated smart pointer). This is much more flexible - both for testing (as you mentioned) but also just in terms of normal usage.

Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373
  • Regarding your point about only using references when there is no reason to ever change them - Couldn't I accomplish the same thing with pointers by using a const pointer? (Object * const m_ptr <-- This pointer can't be changed.) If so, then it seems like there's even less of a reason to use references... – Runcible Jun 05 '09 at 17:42
  • @Runcible: True, although the usage of references is a bit "nicer" than the usage of pointer members, so if you really want that behavior, references are often cleaner to work with. I very rarely use references for members, though - it's just too inflexible in most cases. – Reed Copsey Jun 05 '09 at 17:51
  • I see. And what about NULL checking? Given that you're using pointer members, do you have to do NULL checking all over the place? I've been told that too much NULL checking just amounts to paranoid programming and additional code bloat. I'm curious what your approach is. – Runcible Jun 05 '09 at 18:03
  • 1
    It depends - when I'm using exceptions, I try to wrap my assignment and put the NULL checking into the assignment function, but not necessarily in usage. This gives you the best of both worlds - all of the checking at assignment time, but none of the need for it as you use it, since it's guaranteed to be assigned (or you threw before). – Reed Copsey Jun 05 '09 at 18:30
  • I would also add in here that const int* const is far more ugly/confusing then const int& and there are quite a few times I use that. – Hippiehunter Jun 05 '09 at 19:35
  • @Hippiehunter: Just curious, why do you say it's ugly/confusing? Using them is "m_obj->func()" instead of "m_obj.func()," but that's not that big of a difference. Are you saying that you use "int * const" even though it's ugly, or are you saying that you prefer to use "const int &" because it's less ugly? – Runcible Jun 06 '09 at 19:03
  • @Reed Copsey: Thanks for the insight! Just to add on to this, I found an article by Misko Hevery about NULL checking: http://misko.hevery.com/2009/02/09/to-assert-or-not-to-assert/ – Runcible Jun 06 '09 at 19:05
9

It is almost never prefereable to store references as data members, and a lot of the time it is impossible. If the objects must be assignable (as they must to be stored in a standard library container), references cannot be used. Also, references cannot be reseated, so once a reference is initialised with an object, it cannot be made to refer to a different object.

See this question Should I prefer pointers or references in member data? for a more detailed discussion of the issue.

Community
  • 1
  • 1
3

I was trying to figure this out myself, so might as well post it. I conclude it doesn't seem to be a good idea to use reference data member because you could inadvertently create an alias when you go to initialize it.

#include <iostream>
using namespace std;
class stuff
{
public:
explicit stuff(int &a):x(a) //you have to initialize it here
{
//body intialization won't work
};
int& x; //reference data member
};

int main()
{
int A=100; 
stuff B(A);//intialize B.x
cout<<B.x<<endl;//outputs 100
A=50;//change A;
cout<<B.x<<endl; //outputs 50, so B.x is an alias of A.
system("pause");
return 0;
}
Bill
  • 31
  • 1
1

Given a choice, I like to use the most constrained type possible. So if I don't need to support null objects I'd much prefer to declare a

Foo& m_foo;

member rather than a

Foo*const m_foo;

member, because the former declaration documents the fact that m_foo can't be null. In the short term, the advantage isn't that great. But in the long run, when you come back to old code, the instant assurance that you don't have to worry about the case of m_foo being null is quite valuable.

There are other ways of achieving a similar effect. One project I worked on where they didn't understand references would insist any potentially null pointers be suffixed '00' e.g m_foo00. Interestingly, boost::optional seems to support references although I haven't tried it. Or you can litter your code with assertions.

timday
  • 24,582
  • 12
  • 83
  • 135
  • Foo& m_foo; Happy debugging after accidental re-assignment. Btw, having a reference doesn't guarantee a valid object. It just gives you false confidence. – Sergei Jul 02 '20 at 16:33
  • @Sergei: But you can't reassign a reference. See e.g https://stackoverflow.com/a/728272/24283 . Not sure there's much you can do about the other issue in C++, but that can affect pointers too. (Bear in mind this answer was written in 2009 before smart pointers were part of the standard and raw pointer usage was rife in most codebases). Of course these days there are more opportunities than ever to shoot yourself in the foot with lambda bindings. – timday Jul 02 '20 at 21:20
  • m_foo = something; will overwrite the original object. You really don't want to store non-const references. I suppose it was more like a typo and not a suggestion. – Sergei Jul 03 '20 at 22:18
  • A const Foo& m_foo; would prohibit me from invoking a non-const Foo::ping() method of the Foo object though, so that's not a universal solution. Maybe using a pointer instead does indeed make certain things more explicit, but then maybe your code ends up littered with not-null assertions/checks which could have been avoided by using the reference type to enforce that at compile time. C++ is an unusual language in that it gives you the choice (c.f C: pointers only, or Java/Python etc references only)... but much of that choice is a matter of taste (or whatever the local style guide says). – timday Jul 03 '20 at 23:43
0

Adding to this question..

Class with reference data member:

  • you must pass a value to the object at construction (not unexpectedly)
  • breaks the rule of encapsulation, as referenced variable can be changed from outside class, without class object having any control of it. (I suppose the only use case could be something like this though, for some very specialized reasons.)
  • prevents creating assignment operator. What are you going to copy?
  • you need to ensure the referred variable is not destroyed while your object is alive
KeyC0de
  • 4,728
  • 8
  • 44
  • 68