Pointing the Way Redux, Part 1: Value Semantics

[Update: See below about taking sink arguments by value.]

So a while back I posted about some issues that I found with my co-workers code while doig a review. At the end of the post I said that I’m going to go into excruciating detail about when I think its approproate to use each of the reference types I mentioned in the post. Here’s the first post; you were warned.

Let me preface this by saying that really I’m writing this to force myself to think about how I’m using all this stuff in detail; hopefully I won’t make too big of an ass of myself. Of course, at this point we’re all still figuring out how to use all the new stuff we got in C++11 (not to mention C++14) so maybe this will be helpful. Feel free to jump off the bus at any time.

Let’s start with the object itself, aka value sematics. As a return value they’re generally something to avoid if the object is bigger than a few int’s since you’ll be doing a lot of unescessary copying. Move semantics can take some of the sting out of this, and if what you’re returning isn’t just a copy of a member variable and it is moveable then return by value makes perfectly good sense. If you’re releasing a member vairable (i.e. you’re not using it anymore) then returning by value with a move also makes sense. If you aren’t presented with one of these cases then you’ll probably want to avoid returning by value.

Thread synchronization concerns can require you to return something by value though. If you’re providing access to a member variable in a read-only manner – and the returned object doesn’t do any thread synchronization itself – then you’ll need to return by value so that the caller gets a copy. If you were to return a const reference then another thread could come through and modify the object while the reference is in use causing all manner of undefined behavior. For large objects, or any object that is expensive to copy, using a shared_ptr to a const object is normally a better idea than returning the object by value. Of course being able to do this is predicated on the object being stored in a shared_ptr to begin with – something I’ll get into in a later post.

Unless you’re returning by move there isn’t really an ownership transfer going on when you return by value. The caller will take ownership of the object that you return, but it’s a copy so you still own the original. Obviously if you return by move then you are transferring ownership to the object to the caller. Either way the caller ends up owning what you return, the final disposition of the original object is what differs.

There is a case where you absolutely cannot return by value though: polymorphic objects. Say you have a function that is declared to return a certain class by value and that class has virtual functions. In that function you create a derived class object and return it. Since you’re returning by value the returned object has to be a base class object and this leaves the compiler with no choice but to slice the object down to the base class. There is no way to return a polymorphic object by value and maintain its identity. Instead you need to use a reference or some sort of pointer if polymorphic identity is important (and I can’t think of any cases where its not).

[Update: The following paragraph no longer applies to functions other than constructors. I’ll have details in a future post, but for now suffice it to say that passing sink arguments by values, except for constructors, can lead to unnecessary memory deallocations. Instead one should just take a const reference and copy. If there’s a performance need then make a separate overload taking a r-value reference.]

As with return values, passing objects bigger than a few int’s used to be generally frowned upon (unless you needed a copy of the object to modify in the function without modifying the caller’s copy). In this age of move semantics those waters have been muddied a bit. If the object that is being passed in is going to be stored in a member variable – and it is moveable – then taking the object by value is the way to go; just remember to use moves from that point on when propagating the object to its final resting place. In this case you’re making a copy whether you take the object by value or reference, but in taking it by value you move the copy operation up the stack to a spot where the caller can move an object into the function argument. If the caller takes advantage of this opportunity then all copies have been done away with. For example, where this used to be done:

void some_class::mem_func(const foo& f)
{
    m_mem_var = f; //copy happens here
}

You should now be doing

void some_class::mem_func(foo f) //copy happens at call site
{
    m_mem_var = std::move(f);
}

This allows the caller to call mem_func like mem_func(std::move(some_foo)) which would move some_foo into m_mem_var with no copies being made. In the worst case scenario the caller doesn’t have an object that he wants to give up and a copy is made, but that copy would have been made when using the pre-move version of the function too so we’re no worse off.

Unless you have an overriding concern most member variables should just be objects. Overriding concerns would be: sharing ownership (use shared_ptr), monitoring lifetime (use weak_ptr), nullabililty (use unique_ptr or boost::optional), or performance (i.e. avoiding copies) when the object’s lifetime is guaranteed (use reference or bare pointer depending on nullability concerns). In the absence of these concerns using regular objects as member variables will normally make your life easier: no need to worry about memory allocation and de-allocation, whether something is null or not, object lifetime, and so on.

Next up: references.

Advertisements

8 comments

  1. […] Continuing on with my series going into way more detail than anyone probably wants to go into about which reference type to use when, we now arrive at part 2: references (see motivation and part 1). […]

  2. […] I’ve now started the series threatened […]

  3. […] too much detail about what type of reference to use when is bare pointers (see motivation and parts 1 and 2). Even though we now have an abundance of smart pointers to choose from – about which […]

  4. […] far in my series on what reference types to use when we’ve seen values, references and bare pointers. The first makes a strong statement about ownership when used as a […]

  5. […] far we’ve covered Values, references, bare pointers, and unique_ptr in my more info then you ever wanted about which […]

  6. […] in my more info then you ever wanted about which reference to use when series that has also covered values, references, bare pointers, and unique_ptr. This time I’ll be covering the last of the smart […]

  7. […] more info then you ever wanted about which reference to use when series – that has also covered values, references, bare pointers, shared_ptr and unique_ptr – I covered the last of the smart pointers: […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: