Chris
Chris

Reputation: 11

C++ obtaining a reference to a returned object

I need to grab an object by reference and I used to do it like so:

MyObject& obj = FactoryThatGivesAnObject();
obj.MethodThatModifieObj();

No I need to do it based on a conditional:

MyObject obj;

// Need obj to be a reference to the returned values below
if( foo )
    obj = FactoryThatGivesAnObject();
else
    obj = OtherFactoryThatGivesAnObject();

obj.MethodThatModifiesObj();

How can I have obj be a reference in the second example?

Upvotes: 1

Views: 203

Answers (5)

tp1
tp1

Reputation: 1207

Here's one solution which is not technically a factory, but solves the same problem -- providing new objects when you change parameters:

struct A
{
  int a;
  float x;
  int c;
};
class ObjectCollection
{
public:
    ObjectCollection() { m_a.c = 10; }
    A &get_obj_a(int a, float x)
    {
    m_a.a = a;
    m_a.x = x;
    return m_a;
    }
private:
    A m_a;
};

This version has advantage that it does not pass around ownership to the object, but still you can create different kinds of objects with it. Two calls to get_obj_a() will cause problems though, it only works if you call get_obj_a() immediately before you need the object. Now the if statement can be put inside the factory function. Also here's another way to do it:

class DerivedFactory
{
public:
   DerivedFactory(ObjectCollection1 &c, ObjectCollection2 &c2) : c(c),c2(c2) { }
   Base &get_obj_a_or_b(bool b) {
         if (b) return c.get_obj_a(10,11.0);
         else return c2.get_obj_b(20.0,13.0);
   } 
private:
  ObjectCollection1 &c;
  ObjectCollection2 &c2;
};

Upvotes: 0

Tim
Tim

Reputation: 9172

References, unlike pointers, can only be set once. This is a useful feature many times, but this is the one frustrating aspect about it. You only want to set the reference once, but possibly to different things.

You have two options.

1) Use the ternary operator

This is often the easiest, if you're only dealing with two factories, and a simple boolean to decide which to use:

MyObject& obj = ( foo
                  ? FactoryThatGivesAnObject();
                  : OtherFactoryThatGivesAnObject() );

However, if foo is more complicated, or if you have multiple factory options, the next option may be cleaner.

2) Use a factory method of your own

MyObject& get_an_object(const int state) // or whatever parameters you need
{
    switch(state)
    {
       case USE_LEGACY_FACTORY:    return FactoryThatGivesAnObject();
       case USE_FOO_FACTORY:       return OtherFactoryThatGivesAnObject();
       case DO_SOMETHING_ELSE:     return YetAnotherObjectFactory();
    }
    throw std::runtime_error("Bad Factory Selector");
}

// usage is simpler now
MyObject& obj = get_an_object(foo);

Note that you may need to pass several parameters to your factory method:

  • selection criteria. Your example was just foo - a simple boolean. As things grow, you may need additional criteria to help determine which factory to use.
  • factory objects. You may have factory objects instead of factory methods, in which case you need to pass references to those objects into your method.

Upvotes: 5

Kerrek SB
Kerrek SB

Reputation: 476950

Your very first line is shady:

MyObject& obj = FactoryThatGivesAnObject();

How is that supposed to work? The factory method cannot return a reference to a temporary, so the only sensible reference it could return is to a dynamically created object - but now who is responsible for this object?

(Unless you are just returning a reference to an existing object, that is. But I'm assuming that your factory is genuinely creating new objects.)

This code is a memory-leak car crash; I don't see any way to write anything sensible like that. A far better way is to return the newly created object in a responsible container, e.g. a shared_ptr or a unique_ptr:

#include <memory>

std::unique_ptr<MyObject> FactoryFunction()
{
  return std::unique_ptr<MyObject>(new MyObject(3,5,7));
}

That way, if nobody picks up the factory product, or if an exception occurs, the dynamically allocated object will get properly disposed of.

This also makes it trivial to assign different pointers depending on a conditional:

std::unique_ptr<MyObject> x;

if (...)      { x = Factory1(); }
else if (...) { x = Factory2(a,b); }
else          { x = Factory3(argc, argv); }

Upvotes: 2

Billy ONeal
Billy ONeal

Reputation: 106530

How can I have obj be a reference in the second example?

You can't. References are aliases; you can only create them by pointing them at something, and once you've pointed them, they cannot be reassigned.

You would probably be better off using something like a std::auto_ptr or std::unique_ptr here. Note that your factory would need to return the auto/unique_ptr though. If your factory is returning a reference I suspect you might be accidentially returning references to unnamed temporaries (unedefined behavior), but without seeing the factories' code it's difficult to tell.

Upvotes: 1

Pierre Bourdon
Pierre Bourdon

Reputation: 10880

One solution may be to use a ternary operator:

obj = foo ? FactoryThatGivesAnObject() : OtherFactoryThatGivesAnObject();

You could also use a pointer:

MyObject* pobj;
if( foo )
    pobj = &FactoryThatGivesAnObject();
else
    pobj = &OtherFactoryThatGivesAnObject();

Upvotes: 3

Related Questions