对于使用聚合初始化的结构,避免在make_unique / make_shared / emplace / etc中进行额外移动 [英] Avoiding extra move in make_unique/make_shared/emplace/etc for structures that use aggregate initialization
问题描述
std :: make_unique()
(以及类似的函数)有些问题:
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() ) };
}
}
输出:
--------------- case 1 ---------------
ctor
mctor
dtor
dtor
--------------- case 2 ---------------
ctor
dtor
如您所见,我们还有一个可以避免的额外举措。 emplace()$ c $ 存在问题c>在可选/变量/等中-如果对象被其他函数返回,则必须移动它。
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.
这可以是以一种技巧解决了:
#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(); } );
}
这避免了不必要的动作(enable_if隐藏了构造函数,除非 f()
返回S的实例。您实际上通过调用,在 std :: variant
/ std :: optional
/ etc内构造了值您的构造函数。
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...
我希望能够有效地将我的结构放入可选/ variant / shared_ptr-block /等中,而不会破坏(宁可非-
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 :所有MSVC版本都无法处理从Barry的<$ c $转义的异常c>工厂。在此处中查看详情>。
Edit: All MSVC versions can't handle exceptions escaping from Barry's factory
. See details here.
推荐答案
不是在您的类型中添加带有工厂函数的构造函数,而是使用将转换运算符转换为您的类型。使用C ++ 17,只需很少的工作:
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>;
对于您先前的示例, S
不会不再需要受限的构造函数。您可以改为:
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}
仅打印 ctor
和 dtor
。由于我们不会以任何方式修改 S
,因此我们也可以在汇总中使用它,例如 D
。
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
.
这篇关于对于使用聚合初始化的结构,避免在make_unique / make_shared / emplace / etc中进行额外移动的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!