使用unique_ptr作为值初始化静态std::map

10

如何初始化静态映射,其中值为std::unique_ptr

static void f()
{
    static std::map<int, std::unique_ptr<MyClass>> = {
        { 0, std::make_unique<MyClass>() }
    };
}

当然不行(std::unique_ptr的拷贝构造函数被删除了)。
有可能吗?

你必须为一个东西提供一个变量名。 - Galik
3个回答

11
问题在于使用 std::initializer-list 构造对象会复制其内容。(std::initializer_list 中的对象天然为 const)。 要解决这个问题:您可以从一个单独的函数中初始化映射表...
std::map<int, std::unique_ptr<MyClass>> init(){
    std::map<int, std::unique_ptr<MyClass>> mp;
    mp[0] = std::make_unique<MyClass>();
    mp[1] = std::make_unique<MyClass>();
    //...etc
    return mp;
}

然后称之为

static void f()
{
    static std::map<int, std::unique_ptr<MyClass>> mp = init();
}

Coliru上观看实时演示


如果init()返回std::map<...>&&(rvalue),会有什么不同吗?也就是说,返回rvalue会更好吗?更新:使用rvalue会导致段错误。 - vladon
1
@vladon,这不仅是性能优化,而且还是一个错误。你将会返回一个已经销毁的对象的rvalue引用。请参考这里……在这种情况下,通过值返回是非常高效的,因为它(在C++17中)肯定会享受RVO - WhiZTiM

2

编写定制的创建代码似乎很无聊,而且会影响代码的清晰度。

这里提供了相当高效的通用容器初始化代码。它将您的数据存储在临时std::array中,就像初始化列表一样,但它是移动而不是使其成为const

make_map接受偶数个元素,第一个是键,第二个是值。

template<class E, std::size_t N>
struct make_container_t{
  std::array<E,N> elements;
  template<class Container>
  operator Container()&&{
    return {
      std::make_move_iterator(begin(elements)),
      std::make_move_iterator(end(elements))
    };
  }
};
template<class E0, class...Es>
make_container_t<E0, 1+sizeof...(Es)>
make_container( E0 e0, Es... es ){
  return {{{std::move(e0), std::move(es)...}}};
}

namespace details{
  template<std::size_t...Is, class K0, class V0, class...Ts>
  make_container_t<std::pair<K0,V0>,sizeof...(Is)>
  make_map( std::index_sequence<Is...>, std::tuple<K0&,V0&,Ts&...> ts ){
    return {{{
      std::make_pair(
        std::move(std::get<Is*2>(ts)),
        std::move(std::get<Is*2+1>(ts))
      )...
    }}};
  }
}
template<class...Es>
auto make_map( Es... es ){
  static_assert( !(sizeof...(es)&1), "key missing a value?  Try even arguments.");
  return details::make_map(
    std::make_index_sequence<sizeof...(Es)/2>{},
    std::tie( es... )
  );
}

这应该可以简化为:
static std::map<int, std::unique_ptr<MyClass>> bob = 
  make_map(0, std::make_unique<MyClass>());

涵盖拼写错误的内容除外。

实时示例


@U.Bulle 我修复了两个小错别字,并添加了一个链接到一个可以编译的实例。 - Yakk - Adam Nevraumont
我误点了一下,把我的评论删掉了。为了历史的记录,我在这里评论指出了details::make_map中解包命令编译失败并给出了std::pair错误的详细信息。你是对的,现在它可以编译了。不过我也很想能够在我的代码中使用它,但我不知道为什么它在这里https://dev59.com/uVQJ5IYBdhLWcg3weVq7失败了,可能是VStudio编译器的一个bug。无论如何,非常有趣和酷的解决方案,谢谢! - U. Bulle
@U.Bulle 它在 MSVC 中编译通过?我不知道为什么它在你的环境中无法编译。 - Yakk - Adam Nevraumont

1
另一种方法是使用lambda表达式。它与使用单独的函数相同,但将映射的初始化放得更靠近操作。在这种情况下,我使用了auto和decltype的组合来避免命名地图的类型,但那只是为了好玩。
请注意,传递给lambda的参数是一个对象的引用,在调用点尚未构造该对象,因此我们不能以任何方式引用它。它仅用于类型推断。
#include <memory>
#include <map>
#include <utility>

struct MyClass {};


static auto& f()
{
  static std::map<int, std::unique_ptr<MyClass>> mp = [](auto& model)
  {
    auto mp = std::decay_t<decltype(model)> {};
    mp.emplace(0, std::make_unique<MyClass>());
    mp.emplace(1, std::make_unique<MyClass>());
    return mp;
  }(mp);
  return mp;
}

int main()
{
  auto& m = f();
}

这里还有另一种方式。在这种情况下,我们将临时对象传递到lambda中,并依靠复制省略/RVO。

#include <memory>
#include <map>
#include <utility>

struct MyClass {};

static auto& f()
{
  static auto mp = [](auto mp)
  {
    mp.emplace(0, std::make_unique<MyClass>());
    mp.emplace(1, std::make_unique<MyClass>());
    return mp;
  }(std::map<int, std::unique_ptr<MyClass>>{});
  return mp;
}

int main()
{
  auto& m = f();
}

还有一种方法,使用可变 lambda 中的 lambda 捕获。

#include <memory>
#include <map>
#include <utility>

struct MyClass {};

static auto& f()
{
  static auto mp = [mp = std::map<int, std::unique_ptr<MyClass>>{}]() mutable
  {
    mp.emplace(0, std::make_unique<MyClass>());
    mp.emplace(1, std::make_unique<MyClass>());
    return std::move(mp);
  }();
  return mp;
}

int main()
{
  auto& m = f();
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接