jeudi 27 juillet 2017

Avoiding extra move in make_unique/make_shared/emplace/etc for structures that use aggregate initialization

std::make_unique() (and similar functions) have a little problem:

#include <cstdio>
#include <memory>

using namespace std;

struct S
{
    S()         { printf("ctor\n"); }
    ~S()        { printf("dtor\n"); }
    S(S const&) { printf("cctor\n"); }
    S(S&&)      { printf("mctor\n"); }
};

S foo() { return S(); }

int main()
{
    {
        printf("--------------- case 1 ---------------\n");
        unique_ptr<S> s1 = make_unique<S>( foo() );
    }

    {
        printf("--------------- case 2 ---------------\n");
        unique_ptr<S> s2 { new S( foo() ) };
    }
}

Output:

--------------- case 1 ---------------
ctor
mctor
dtor
dtor
--------------- case 2 ---------------
ctor
dtor

As you see we have an extra move that can be avoided. Same problem exists with emplace() in optional/variant/etc -- if object gets returned by other function, you have to move it.

This can be addressed with a trick:

#include <cstdio>
#include <optional>

using namespace std;

struct S
{
    S()         { printf("ctor\n"); }
    ~S()        { printf("dtor\n"); }
    S(S const&) { printf("cctor\n"); }
    S(S&&)      { printf("mctor\n"); }

    template<class F, enable_if_t<is_same_v<invoke_result_t<F>, S>>...>
    S(F&& f) : S(forward<F>(f)()) {}
};

S foo() { return S(); }

int main()
{
    optional<S> s;
    s.emplace( []{ return foo(); } );
}

This avoids unnecessary move (enable_if hides constructor unless f() returns an instance of S). You effectively end up constructing your values inside of std::variant/std::optional/etc via a call to your constructing function.

This fix has a little problem -- adding a constructor breaks aggregate initialization. See example. I.e. if given structure had no constructor and you add one -- you can no longer initialize it like this:

struct D
{
    float m;
    S s;

    // adding new constructor here will break existing bar() functions
};

D bar() { /*...lots of code with multiple return statements...*/ return {2.0, foo()}; }

Question: Is there a way around this problem? Something that doesn't introduce new constructors...

I'd like to be able to efficiently put my structures into optional/variant/shared_ptr-block/etc without breaking (rather non-trivial) code that creates them.

Aucun commentaire:

Enregistrer un commentaire