Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I utilize move semantics when returning a member variable?

I'm implementing a factory class which builds a vector of uint8_t. I want to be able to utilize move semantics when returning the resulting vector. This seems to work but I'm not confident this is the correct way of accomplishing what I want.

I have seen quite a few examples of how a returned automatic variable will be regarded as an rvalue and use the move constructor of the calling code, but in my example, the returned object is a member. I know the member will loose its contents if the caller puts the return value into a move constructor - and this is just what I want.

I have written it something like this:

#include <cstdint>
#include <iostream>
#include <vector>

class Factory
{
public:
    std::vector<uint8_t> _data;

    Factory(std::size_t size) :
        _data(size, 0)
    {
    }

    void buildContent(int param)
    {
        // perform operations on the contents of _data
    }

    std::vector<uint8_t> && data()
    {
        return std::move(_data);
    }
};

int main()
{
    Factory factory(42);
    factory.buildContent(1);
    std::vector<uint8_t> temp(factory.data());
    std::cout << "temp has " << temp.size() << " elements" << std::endl;
    std::cout << "factory._data has " << factory._data.size() << " elements" << std::endl;

    return 0;
}

Edit:

Oh, and the example code outputs the following:

temp has 42 elements
factory._data has 0 elements
like image 866
anorm Avatar asked Oct 02 '13 09:10

anorm


People also ask

What's the need to move semantics?

Move semantics allows you to avoid unnecessary copies when working with temporary objects that are about to evaporate, and whose resources can safely be taken from that temporary object and used by another.

Should I always use std move?

Q: When should it be used? A: You should use std::move if you want to call functions that support move semantics with an argument which is not an rvalue (temporary expression).

What does std:: move return?

std::move is an identity operation. It never actually does anything. It's just a marker for rvalues. If the compiler has the move constructor of Foo on hand it can see if it has observable effects and decide upon that.


2 Answers

If your compiler has rvalue references to this (&& after methods), you might want to use that. See @juanchopanza's answer.

If you don't, you first want to make sure that data() makes it clear that you are moving. There are a few ways to do this.

First, non-member methods (friends) can override on &&. So you can get this syntax:

std::vector<uint8_t> temp(get_data( std::move(factory) );

where get_data has && and & overloads on your factory type, and either moves or not based off it.

Next, you want to return a std::vector<uint8_t> instead of a `std::vector<uint8_t>&& for lifetime extension issues. The runtime cost is somewhere between zero and small, but the bugs eliminated are worthwhile.

If create_factory returns a Factory object, then if we do this:

for( uint8_t x : get_data( create_factory() ) )

the get_data that returns a && doesn't work, while the one that returns a temporary works flawlessly.

What is going on? Well, ranged-based for loops are defined as binding the thing you are iterating over to a reference. A temporary bound to a reference has its lifetime extended: a reference bound to a reference has no lifetime extension. The create_factory function's return value's lifetime is not extended in either case.

With the && case, the reference to vector is left dangling. With the return-temporary case, the factory's vector is moved into the temporary, then that temporaries lifetime is extended.

In short, returning && references is very rarely a good idea.

like image 186
Yakk - Adam Nevraumont Avatar answered Oct 17 '22 07:10

Yakk - Adam Nevraumont


First of all, you have to decide what you want. Do you really want to suck data out of your instance? If so, what you have achieves that fine. On the other hand, it looks quite unsafe. What you could do is use reference qualifiers to enforce that you only return an rvalue reference when the instance itself is an rvalue reference:

std::vector<uint8_t> && data() && // called on rvalue
{
    return std::move(_data);
}

// return lvalue ref or value
std::vector<uint8_t>& data() & // called on lvalue
{
    return _data;
}

In the end, it all depends on which problem you are trying to solve, which is not apparent from your question.

like image 30
juanchopanza Avatar answered Oct 17 '22 07:10

juanchopanza