C++ return type when I don't know if it's

2019-06-15 07:35发布

问题:

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.

回答1:

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.



回答2:

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<>.



回答3:

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



回答4:

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.