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 без разбивки (довольно нетривиального) кода, который их создает.