13

After reading a question on the difference between pointers and references, I decided that I'd like to use references instead of pointers for my class fields. However it seems that this is not possible, because they cannot be declared uninitialized (right?).

In the particular scenario I'm working on right now, I don't want to use normal variables (what's the correct term for them by the way?) because they're automatically initialized when I declare them.

In my snippet, bar1 is automatically instantiated with the default constructor (which isn't what I want), &bar2 causes a compiler error because you can't use uninitialized references (correct?), and *bar3 is happy as larry because pointers can be declared uninitialized (by the way, is it best practice to set this to NULL?).

class Foo
{
public:
    Bar bar1;
    Bar &bar2;
    Bar *bar3;
}

It looks like I have to use pointers in this scenario, is this true? Also, what's the best way of using the variable? The -> syntax is a bit cumbersome... Tough luck? What about smart pointers, etc? Is this relevant?

Update 1:

After attempting to implement a reference variable field in my class and initializing it in the constructor, why might I receive the following error?

../src/textures/VTexture.cpp: In constructor ‘vimrid::textures::VTexture::VTexture()’:
../src/textures/VTexture.cpp:19: error: uninitialized reference member ‘vimrid::textures::VTexture::image’

Here's the real code:

// VTexture.h
class VTexture
{
public:
    VTexture(vimrid::imaging::ImageMatrix &rImage);
private:
    vimrid::imaging::ImageMatrix ℑ
}

// VTexture.cpp
VTexture::VTexture(ImageMatrix &rImage)
    : image(rImage)
{
}

I've also tried doing this in the header, but no luck (I get the same error).

// VTexture.h
class VTexture
{
public:
    VTexture(vimrid::imaging::ImageMatrix &rimage) : image(rImage) { }
}

Update 2:

Fred Larson - Yes! There is a default constructor; I neglected it because I thought it wasn't relevant to the problem (how foolish of me). After removing the default constructor I caused a compiler error because the class is used with a std::vector which requires there to be a default constructor. So it looks like I must use a default constructor, and therefore must use a pointer. Shame... or is it? :)

Community
  • 1
  • 1
Nick Bolton
  • 38,276
  • 70
  • 174
  • 242
  • "normal variables (what's the correct term for them by the way?)" you can call them 'stack variables' – veefu Apr 10 '09 at 13:13
  • "normal variables" --> "Primitive Data Types" – Robert Deml Apr 10 '09 at 13:19
  • primitive data types aren't the only normal variables... you can have user defined data types as normal variables too. – Shree Apr 10 '09 at 13:20
  • Hmm, when I say "normal variables" I mean, "not a pointer and not a reference", so I'm referring to Foo::bar1 as a "normal variable" - is this what you guys are talking about? – Nick Bolton Apr 10 '09 at 13:22
  • you call it members, pointer member or reference member. – Shree Apr 10 '09 at 13:30
  • Ah, so in C++ the term is "member" and not "field" as it is in C#? – Nick Bolton Apr 10 '09 at 13:33
  • um, you can initialize the reference with the default constructor too... In fact, you must. – Evan Teran Apr 10 '09 at 13:53
  • r3n: the term for your use of "normal variables" is "member objects" as opposed to "member pointers", "member references", "locals", "stack variables", etc. –  Apr 10 '09 at 14:22
  • The issue with vector requiring a default constructor is a specific library issue I believe and not necessarily a c++ standard - if you don't use the methods that require a default constructor like operator [], then you don't need it. – Greg Domjan Apr 10 '09 at 14:23
  • Greg: vector::op[] doesnt require a default ctor. you're thinking of std::map. "struct A{A(int){}};int main(){vector v;v.push_back(A(5));v[0];}" compiles. –  Apr 10 '09 at 21:08
  • I looked at LLVM implementation of `type_index` cass. The constructor takes reference to `type_info` but the internal private field which "holds" it is a pointer to `type_info` object. Why they use Pointer instead of Reference internally? https://github.com/llvm/llvm-project/blob/main/libcxx/include/typeindex#L57 Alternative impl which uses Reference https://gist.github.com/apivovarov/dcf94ea8861efd25f145650e2acbb2fb#file-typeindex-h-L57 – x4444 Dec 18 '20 at 02:16

6 Answers6

17

Answer to Question 1:

However it seems that this is not possible, because they [references] cannot be declared uninitialized (right?).

Right.


Answer to Question 2:

In my snippet, bar1 is automatically instantiated with the default constructor (which isn't what I want), &bar2 causes a compiler error because you can't use uninitialized references (correct?),

You initialize references of your class in your constructor's initializer list:

class Foo
{
public:
    Foo(Bar &rBar) : bar2(rBar), bar3(NULL)
    {
    }

    Bar bar1;
    Bar &bar2;
    Bar *bar3;
}

Answer to Question 3:

In the particular scenario I'm working on right now, I don't want to use normal variables (what's the correct term for them by the way?)

There is no correct name for them, typically you can just say pointers for most discussions (except this one) and everything you need to discuss will also apply to references. You initialize non pointer, non reference members in the same way via the initailizer list.

class Foo
{
public: 
  Foo()  : x(0), y(4)
  {
  }

  int x, y;
};

Answer to Question 4:

pointers can be declared uninitialized (by the way, is it best practice to set this to NULL?).

They can be declared uninitialized yes. It is better to initialize them to NULL because then you can check if they are valid.

int *p = NULL;
//...

//Later in code
if(p)
{
  //Do something with p
}

Answer to Question 5:

It looks like I have to use pointers in this scenario, is this true? Also, what's the best way of using the variable?

You can use either pointers or references, but references cannot be re-assigned and references cannot be NULL. A pointer is just like any other variable, like an int, but it holds a memory address. An array is an aliased name for another variable.

A pointer has its own memory address, whereas an array should be seen as sharing the address of the variable it references.

With a reference, after it is initialized and declared, you use it just like you would have used the variable it references. There is no special syntax.

With a pointer, to access the value at the address it holds, you have to dereference the pointer. You do this by putting a * before it.

int x=0;
int *p = &x;//p holds the address of x
int &r(x);//r is a reference to x
//From this point *p == r == x
*p = 3;//change x to 3
r = 4;//change x to 4
//Up until now
int y=0;
p = &y;//p now holds the address of y instead.

Answer to Question 6:

What about smart pointers, etc? Is this relevant?

Smart pointers (See boost::shared_ptr) are used so that when you allocate on the heap, you do not need to manually free your memory. None of the examples I gave above allocated on the heap. Here is an example where the use of smart pointers would have helped.

void createANewFooAndCallOneOfItsMethods(Bar &bar)
{
    Foo *p = new Foo(bar);  
    p->f();
    //The memory for p is never freed here, but if you would have used a smart pointer then it would have been freed here.  
}

Answer to Question 7:

Update 1:

After attempting to implement a reference variable field in my class and initializing it in the constructor, why might I receive the following error?

The problem is that you didn't specify an initializer list. See my answer to question 2 above. Everything after the colon :

class VTexture
{
public:
    VTexture(vimrid::imaging::ImageMatrix &rImage)
      : image(rImage)
    { 
    }
private:
    vimrid::imaging::ImageMatrix ℑ
}
Brian R. Bondy
  • 339,232
  • 124
  • 596
  • 636
13

They can be initialized. You just have to use the member initializer list.

Foo::Foo(...) : bar1(...), bar2(...), bar3(...)
{
  // Whatever
}

It's a good idea to initialize all of your member variables this way. Otherwise, for other than primitive types, C++ will initialize them with a default constructor anyway. Assigning them within the braces is actually reassigning them, not initializing them.

Also, keep in mind that the member initializer list specifies HOW to initialize the member variables, NOT THE ORDER. Members are initialized in the order in which they are declared, not in the order of the initializers.

Fred Larson
  • 60,987
  • 18
  • 112
  • 174
  • Thanks for your answer, I'm having a problem using the member initializer list, please could you take a look at update #1? – Nick Bolton Apr 10 '09 at 13:35
  • Some non-primitive types are uninitialized by default. Examples are POD objects and POD parts of a non-POD structure. For all uninitialized elements, assignment inside the braces is equivalent to inialization. The recommended way is initialization lists. I would give an extra +1 for the last part... – David Rodríguez - dribeas Apr 10 '09 at 13:38
  • From the error message, it appears you have a default constructor that is not initializing the 'image' member. – Fred Larson Apr 10 '09 at 13:39
  • ... which makes an interesting point -- if you have a default constructor, you'll have to find or create some object to use to initialize that reference. If it's valid for it not to refer to anything, it'll have to be a pointer. – Fred Larson Apr 10 '09 at 13:42
  • @Fred Larson: Very well spotted sir! I neglected it because I thought it was unrelated... ironic. – Nick Bolton Apr 10 '09 at 13:46
  • Ok, if it is valid for your VTexture to exist without an image, you will need a pointer. If not, you may wish to provide a default image object (perhaps instantiated in an anonymous namespace) and initialize your reference to that. – Fred Larson Apr 10 '09 at 13:50
2

Use the null object design pattern

I'm using ints but it would be the same with any type.

//header file
class Foo
{
public:
   Foo( void );
   Foo( int& i );
private:
   int& m_int;
};

//source file
static int s_null_Foo_m_i;

Foo::Foo( void ) :
   m_i(s_null_Foo_m_i)
{ }

Foo::Foo( int& i ) :
   m_i(i)
{ }

Now you have to make sure that Foo makes sense when default constructed. You can even detect when Foo has been default constructed.

bool Foo::default_constructed( void )
{
   return &m_i == &s_null_Foo_m_i;
}

I absolutely agree with the sentiment, Always prefer references over pointers. There are two notable cases where you can't get away with a reference member:

  1. Null has a meaningful value.

    This can be avoided with the null object design pattern.

  2. The class has to be assignable.

    The compiler will not generate an assignment operator for classes that have a reference member. You can define one yourself, but you will not be able to change where the reference is bound.

deft_code
  • 57,255
  • 29
  • 141
  • 224
1

There is also a side effect when you define when you define Bar and Bar *

class Foo
{
public:
    Bar bar1; // Here, you create a dependency on the definition of Bar, so the header //file for bar always needs to be included.
    Bar &bar2;
    Bar *bar3; //Here, you create a pointer, and a forward declaration is enough, you don't have to always include the header files for Bar , which is preferred.
}
J.W.
  • 17,991
  • 7
  • 43
  • 76
  • Thanks, so can we use forward declaration with reference members or do we need to include header? I imagine it's the former... – Nick Bolton Apr 10 '09 at 13:38
0
class Foo {
public:
    Bar bar1;
    Bar &bar2;
    Bar *bar3;

    // member bar2 must have an initializer in the constructor
    Bar::Bar(Bar& _bar2) : bar1(), bar2(_bar2), bar3(new Bar()) {}

    Bar::~Bar() {delete bar3;}
}

Note that bar2 isn't just initialized in the ctor; it's initialized with a bar object that's passed in as a reference parameter. That object and the bar2 field will be bound together for the life of the new Foo object. That is usually a very bad idea, because it's hard to ensure that the lifetimes of the two objects will be well coordinated (i.e., that you will never dispose of the passed-in bar object before disposing of the Foo object.)

This is why it's greatly preferred to use either instance variables (as in bar1) or pointers to objects allocated on the heap (as in bar3.)

Dan Breslau
  • 11,472
  • 2
  • 35
  • 44
0

Using references just because the -> syntax is cumbersome isn't the best reason... References have the one great advatage over pointers in that nulls aren't possible without casting trickery, but also disadvantages in initialization and the risk of accidentally illegaly binding temporaries which then go out of scope (for instance, after an implicit conversion).

Yes, smart pointers such as the boost ones are almost always the right answer for handling composite members, and occasionally for associated members (shared_ptr).

Pontus Gagge
  • 17,166
  • 1
  • 38
  • 51
  • Hehe, I knew someone would pick up on the -> syntax remark - thanks for the tip, most helpful. – Nick Bolton Apr 10 '09 at 13:39
  • IIRC, you can only bind a temporary to a const reference, and then the temporary sticks around for the duration of the reference. Assign a pointer to point to a temporary and then you've got problems. References are safer here. – David Thornley Apr 10 '09 at 14:06
  • @David: I've made the same mistake. The lifetime of a temporary bound to a const ref is bounded by the code block. Once you exit the code block (and e.g. return an object with a bound const ref), that reference becomes meaningless. That's why references look safer than pointers, but aren't always. – Pontus Gagge Apr 10 '09 at 14:36
  • See Herb Sutter's GOTW #88 for more details on why member references can be dangerous. – Pontus Gagge Apr 10 '09 at 14:38