Suppose that Foo
is a rather large data structure. How should I write a const
virtual function that returns an instance of Foo
, if I don't know whether the inherited classes will store the instance of Foo
internally; thus, allowing a return by reference. If I can't store it internally, my understanding is I can't return a const
reference to it because it will be a temporary. Is this correct? The two options are:
virtual Foo foo() const { ... }
virtual Foo const & foo() const { ... }
Here's a related question but from a different angle.
You're interested in the difference between a value return and a const reference return solely as a matter of optimization, but it isn't. There's a fundamentally different meaning between returning a different value each time, vs. returning a reference each time, quite possibly to the same object, which quite possibly could be modified:
const Foo &a = myobj.foo();
myobj.modify_the_foo();
const Foo &b = myobj.foo();
a == b; // do you want this to be true or false?
The caller needs to know which it is, both because the programmer needs to know the meaning and because the compiler needs to know the calling convention, so you can't mix them in different overrides of the same virtual function. If some derived classes want to do one, and some want to do the other, then that's tough luck, they can't, any more than one can return an int
and another a float
.
You could perhaps return a shared_ptr
. That way, the derived classes that "want" to return a reference can create a shared_ptr
with a deleter that does nothing (but beware - the shared_ptr
will dangle if the original object is destroyed, and that's not what you normally expect from a returned shared_ptr
. So if it makes sense for the Foo
to outlive the object it came from then it would be better for the class to dynamically allocate it, hold it via a shared_ptr
, and return a copy of that, rather than a do-nothing deleter). The derived classes that "want" to return a value can allocate a new one each time. Since Foo
is "rather large", hopefully the cost of the shared_ptr
and the dynamic allocation isn't too painful compared with what you'd do anyway to create a new value to return.
Another possibility is to turn Foo
into a small pImpl-style class that references a rather large data structure. If everything involved is immutable, then the "want to return a reference" case can share the large data structure between multiple Foo
instances. Even if it isn't, you can think about copy-on-write.
I see that you haven't listed C++0x as a tag, but as reference for anyone with your needs plus access to C++0x, perhaps the best way is to return a std::unique_ptr<>
.
Here's one way:
struct K
{
int ii;
};
class I
{
virtual K &get_k(int i)=0;
};
class Impl : public I
{
K &get_k(int i) { kk.ii = i; return kk; }
K kk;
};
What makes it work is that you use K kk; inside the same object as data member. A constructor for Impl class might be useful too.
EDiT: changing formatting of the code
If you don't know whether a derived class can store the object then you cannot return by reference. So the strict answer to your question is that you must return by value.
Other answers have suggested returning a pointer or a smart pointer, which will work too. However, then a client that does not store the object will have to perform dynamic allocation which can be slower than moving or copying even a reasonably large object.
If your main concern is avoiding the copy then you can achieve that by making your interface a little less nice:
virtual void foo(Foo& putReturnvalueHere) const { ... }
assign the value you would have previously returned into the passed-in reference. This requires Foo to be already constructed. If that is not acceptable, you can pass in a pointer to an un-constructed memory area that will hold a Foo and then use placement new to construct a foo into that memory area:
virtual void foo(Foo* unconstructedFoo) const { ... }
I wouldn't recommend the last idea unless you know what you are doing and you really must have top performance. If performance is that important, you may want to consider avoiding a virtual function call in the first place.