?
Solved

The annoying c++ reference initialization issue.

Posted on 2007-08-12
18
Medium Priority
?
223 Views
Last Modified: 2010-04-01
Hi Experts,

Surely you've all encountered this "reference initialization" problem...

I have a classes Team and Player.  In Team.h I declare five players:

private:
    Player center;
    Player forward1;
    Player forward2;
    ...

Now, in Player.h, I have declared a member reference variable:

private:
    Strategy & default_strategy;

And that's where everything goes to crapola.  The compiler, of course, would like me to initialize default_strategy in Player::Player() : default_strategy = some_strategy { }.  But then I'd have to give every player the same strategy!  In Team.h, the compiler will not allow something like Player center(center_strategy) because it is only the class declaration.  Finally I thought of implementing a member function Player::setStrategy(Strategy &).  But that fails because the comiler _reallY_ wants default_strategy initialized in the default constructor.

AHHH!!!  

Surely there must be a way for one object to have another object as a member variable that has a user-setable member reference variable????

FYI, I'd really like to implment default_strategy as a reference because in my actual code the type is an object inherited from ostream.  And it just makes sense to work with ostream objects as references rather than, say, pointers.  

Also FYI, the setting of default_strategy need not be done at compile time.  That is, I know precisely that I want to set the center's default_strategy to center_strategy, the two forwards' strategies to forward_strategy, etc.

Thanks!

0
Comment
Question by:highqllc
  • 7
  • 5
  • 4
  • +1
18 Comments
 
LVL 86

Assisted Solution

by:jkr
jkr earned 450 total points
ID: 19680234
Why not using a pointer here and initializing it to NULL in the constructor?
0
 

Author Comment

by:highqllc
ID: 19680252
well, in my actual code the object I'd like to get a reference to is an ostream-inhereted object...   so to use it with you pointer suggestion, I'd have to do the equivalent of (*cout) << "blah"  instead of just cout << blah.

Yes, it would work, but is there really no other way???
0
 
LVL 86

Accepted Solution

by:
jkr earned 450 total points
ID: 19680264
Well, references must be initialized, there is no way to skip that. If you want to save work when using a pointer, you could add a convenience member that would

ostream& get() { return *m_pStream;}

so you can write

get() << "blah";
0
Concerto's Cloud Advisory Services

Want to avoid the missteps to gaining all the benefits of the cloud? Learn more about the different assessment options from our Cloud Advisory team.

 

Author Comment

by:highqllc
ID: 19680394
hmm....  and the only way to do the initialization is like this: Player::Player() : default_strategy(some_strategy) { }?   I'd be happy with another way that allowed me to make use of any type of conditional logic on setting the reference... if(blah) default_strategy = strat1; else default_strategy = strat2;
0
 
LVL 86

Expert Comment

by:jkr
ID: 19681004
That you are free to do, e.g.

Player::Player() : default_strategy(blah ? some_strategy : other_strategy) { }

0
 
LVL 39

Assisted Solution

by:itsmeandnobodyelse
itsmeandnobodyelse earned 900 total points
ID: 19682232
>>>> Well, references must be initialized, there is no way to skip that.
You can reference to a object created by new as a pointer.

Player::Player() : default_strategy(*(new Strategy(/*default = */ true))) {}

Later you can update it by a set function.

You get rid of the object created by new in the destructor by

Player::~Player()
{
    ...
    delete &default_strategy;
}

Regards Alex
0
 

Author Comment

by:highqllc
ID: 19682347
That's an interesting suggestion, Alex.  But how would you update it later with a set function?  The only L-value associated with the newly-created memory is default_strategy.   What would you update?  Also, I didn't understand completely this : /*default = */ true, but assume it was not critical to your suggestion.  

Thanks,

kevin
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 19682506
>>>> But how would you update it later with a set function?

void Player::setStrategy(const Strategy& strategy)
{
     default_strategy = strategy;
}

Note, class Strategy needs a operator= for that if it contains member pointers or class members with member pointers (never copy member pointers to different objects but only the contents).

Regards, Alex
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 19682515
>>>> /*default = */ true, but assume it was not critical to your suggestion.
Yes, it only should make clear, that Strategy needs different constructors, one of them is the initial (default) strategy.
0
 
LVL 39

Assisted Solution

by:itsmeandnobodyelse
itsmeandnobodyelse earned 900 total points
ID: 19682553
>>>> the object I'd like to get a reference to is an ostream-inhereted object...  

Hmmm. stream class objects are not suitable to hold them as a member in a class. The reason is that they always have a current status which makes it not easy to 'update' a stream object with another stream. Actually, newer versions of the VC compiler forbid to make 'copies' of a stream object by providing a private copy constructor and a private assignment operator. So, the above sample code would not compile with these compilers because of that.

Instead stream objects should be created newly each time they were needed. In case of a file stream alway open and close a new stream object in the functions where you need them. There are only rare cases where that would make any performance issue. If you can't do it, you better pass the stream object by reference to any function where you need it.
0
 
LVL 9

Assisted Solution

by:jasonclarke
jasonclarke earned 150 total points
ID: 19684518
>>>> But how would you update it later with a set function?
>> void Player::setStrategy(const Strategy& strategy)

this looks very error prone to me...  it seems very likely to lead to unintended behaviour... for example:

Strategy s1(...);  
Strategy s2(...);
Team t(s1);  /// create team set default strategy to s1...
t.setStrategy(s2);  // oops... overwrites s1.... possibly unintentional?

it seems to me that where the object has to change storing a reference is not appropriate - either store an object copy or store a pointer...  
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 19685155
>>>> t.setStrategy(s2);  // oops... overwrites s1.... possibly unintentional?
No, that doesn't happen.

 t.setStrategy(s2);  

passes a const reference of s2 to Player t. In setStrategy we have a own member object allocated by 'new' and

   default_strategy = strategy;

copies the contents (not the reference) of s2 to the default_strategy member.

That is absolutely safe (actually safer than a pointer would be) .... but won't compile if the Strategy was derived from basic_ostream cause the operator= is a private member of one of the member classes.

Regards, Alex


0
 
LVL 9

Expert Comment

by:jasonclarke
ID: 19685263
> No, that doesn't happen.

maybe it would be too easy to have a constructor that did not behave this way, i.e.:

Team(Strategy& s) : default_strategy(s) {}

where the problem would apply.  

There don't seem to be many benefits to me of storing a reference as a class member like this.  You don't really lose anything much by storing an object by value or using a pointer in the class (Especially if you are creating the object using new - why not just store the pointer?).  


0
 

Author Comment

by:highqllc
ID: 19685804
ok ok, I've decided to go with a pointer...    it just looks weird in my code to have stuff around like  *p_cout << blah, rather than: cout << blah
0
 
LVL 86

Expert Comment

by:jkr
ID: 19685847
>>t just looks weird in my code

As I wrote earlier - you could add a convenience member that would

ostream& get() { return *m_pStream;}

so you can write

get() << "blah";
0
 
LVL 39

Assisted Solution

by:itsmeandnobodyelse
itsmeandnobodyelse earned 900 total points
ID: 19686020
>>>> There don't seem to be many benefits to me of storing a reference
>>>> as a class member like this.
The main benefit is that it is always valid, what is the main problem of pointers. Onother benefit is that you don't need dereference. As the only code for new/delete is in the constructors/destructor it is much safer than the get method jkr posted which will return a invalid reference if the pointer is not valid.

>>>> Team(Strategy& s) : default_strategy(s) {}
Actually that is a unsafe and bad method to initialize a reference member. I suggested

   Player::Player()  : default_strategy(*(new Strategy())) {}

what always would generate a valid member.

>>>>> ok ok, I've decided to go with a pointer...    
???

Actually a pointer solves nothing beside of some minor initialization problems. Not a member pointer which was provided outside of the class is really bad OOP. You always should create a copy of the object the pointer is pointing to if using pointer members at all .... and than you'll have the same copy problem as with a reference member.

Can you tell why the Strategy object needs to be derived from a stream class? If you want top read/write from/to a file, it is always better to have a string member containing the file path, and have a temporary stream object when accessing the file.

Regards, Alex
0
 

Author Comment

by:highqllc
ID: 19690938
Hi everyone,

Thanks for all the good comments.  I initially thought the answer could be super-simple, but that's clearly not the case.  I'm probably having these issues due to poor design...    My stream objects behave like cout but when you send data to them, they intellegently position the data on the screen and choose which file to copy the data to. For example,

myout_left << "blah"  

would position "blah" on the left hand side of the screen whereas

myout_right << "blah"

would position it on the right hand side.  

My goal was to create a class that had a reference to one of these stream objects so that I could set where it wrote to one level of abstraction up. But based on this thread, I should probably re-think the design.

Anyways, thanks for all the comments,

kevin
0
 
LVL 39

Expert Comment

by:itsmeandnobodyelse
ID: 19691229
>>>> But based on this thread, I should probably re-think the design.

Yes, I would like to agree to that although I don't know all features you wanted to implement by using the stream members.

Generally, formatting and positioning best were made in overloads of operator<<. But then you don't have

      myout_left << "blah";

but something like

class Player
{
     ...
    friend std::ostream& operator<< (std::ostream& os, const Player& p)
    {
          int offset = p.getOffset();
          if (default_strategy->isLeftPlayer())
          {
                 os << std::endl;
                 os << std::setw(5) << " " << p.getname();  // offset 5
                 offset = 5;
          }
          else /* if (default_strategy->isRightPlayer()) */
          {
                 os << std::setw(65 - offset) << " " << p.getname();  // offset 65
                 offset =  65;
          }
          p.setOffset(offset + p.getname().length());
          return os;
    }
    ...

and later

    Strategy left(true);
    Strategy right(false);
    Player player1("Joe", left);
    Player player2("Mary", right);
    cout << player1 << player2;               // screen
    outputfile << player1 << player2;        // file

   
Generally, operator<< prints the contents of an object to an arbitrary device (stream). So, if you want to print both  to screen and file you need two statements.

Regards, Alex
0

Featured Post

Vote for the Most Valuable Expert

It’s time to recognize experts that go above and beyond with helpful solutions and engagement on site. Choose from the top experts in the Hall of Fame or on the right rail of your favorite topic page. Look for the blue “Nominate” button on their profile to vote.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

When writing generic code, using template meta-programming techniques, it is sometimes useful to know if a type is convertible to another type. A good example of when this might be is if you are writing diagnostic instrumentation for code to generat…
Often, when implementing a feature, you won't know how certain events should be handled at the point where they occur and you'd rather defer to the user of your function or class. For example, a XML parser will extract a tag from the source code, wh…
The viewer will learn how to pass data into a function in C++. This is one step further in using functions. Instead of only printing text onto the console, the function will be able to perform calculations with argumentents given by the user.
The viewer will learn how to clear a vector as well as how to detect empty vectors in C++.

850 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question