Избегайте дополнительного перемещения в make_unique/make_shared/emplace/etc для структур, которые используют агрегатную инициализацию
std::make_unique()
(и аналогичные функции) имеют небольшую проблему :
#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()
в опции /variant/etc - если объект возвращается другой функцией, вам нужно переместить его.
Это может быть обращено с помощью трюка:
#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 через вызов вашей функции построения.
У этого исправления есть небольшая проблема - добавление конструктора прерывает инициализацию агрегата. См. пример. То есть если данная структура не имеет конструктора, и вы добавляете ее, вы больше не можете ее инициализировать следующим образом:
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()}; }
Вопрос: Есть ли способ решить эту проблему? Что-то, что не вводит новые конструкторы...
Я хотел бы эффективно разместить мои структуры в необязательном /variant/shared _ptr-block/etc без разбивки (довольно нетривиального) кода, который их создает.
Ответы
Ответ 1
Вместо добавления конструктора к вашему типу, который использует функцию factory, вместо этого создайте новый внешний объект factory с оператором преобразования в ваш тип. С С++ 17 это требует минимальной работы:
template <class F>
struct factory {
F f;
operator invoke_result_t<F&>() { return f(); }
};
template <class F>
factory(F ) -> factory<F>;
Для вашего предыдущего примера S
больше не нужен конструктор с ограничениями. Вы бы сделали:
optional<S> s;
s.emplace( factory{[]{ return foo(); }} ); // or really just factory{foo}
Что печатает только ctor
и dtor
. Поскольку мы никоим образом не изменяем S
, мы могли бы использовать это также в агрегатах - например, D
.