Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
598 views
in Technique[技术] by (71.8m points)

c++ - 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
"); }
    ~S()        { printf("dtor
"); }
    S(S const&) { printf("cctor
"); }
    S(S&&)      { printf("mctor
"); }
};

S foo() { return S(); }

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

    {
        printf("--------------- case 2 ---------------
");
        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
"); }
    ~S()        { printf("dtor
"); }
    S(S const&) { printf("cctor
"); }
    S(S&&)      { printf("mctor
"); }

    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.


Edit: All MSVC versions can't handle exceptions escaping from Barry's factory. See details here.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

Instead of adding a constructor to your type that takes a factory function, instead create a new external factory object with a conversion operator to your type. With C++17, that takes minimal work:

template <class F>
struct factory {
    F f;

    operator invoke_result_t<F&>() { return f(); }
};

template <class F>
factory(F ) -> factory<F>;

For your earlier example, S doesn't need the constrained constructor anymore. You would instead do:

optional<S> s;
s.emplace( factory{[]{ return foo(); }} ); // or really just factory{foo}

Which prints just ctor and dtor. Since we're not modifying S in any way, we could use this in aggregates as well - like D.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...