【问题标题】:Initialize static std::map with non copyable value in a uniformed inline initialization在统一的内联初始化中使用不可复制的值初始化静态 std::map
【发布时间】:2019-05-17 07:35:12
【问题描述】:

我想初始化一个静态的std::map,其中的值是不可复制的。我会称我的班级为 ValueClassValueClass 有一个 std::unique_ptr 作为私有成员,我什至通过扩展 non_copyable 来确保 ValueClass 不可复制,如下所示:

class non_copyable {
public:
    non_copyable() = default;
protected:
    virtual ~non_copyable() = default;
private:
    non_copyable(const non_copyable&) = delete;
    non_copyable& operator=(const non_copyable&) = delete;
};

现在我正在尝试使用我的类作为值来定义一个 std::map:

static std::map<int, ValueClass> value_classes = {
    {0, ValueClass()},
    {1, ValueClass() }
};

initializer_list 尝试复制此类时出现编译错误。

本周末,我试图在几个小时内编写自己的 make_map 函数以启用初始化而不复制,但我失败了。我试过thisthatother,但它们都不能用Visual Studio 15.9.4 编译。

如何使用 Visual Studio 编译器在不强制复制的情况下初始化静态 std::map,并将初始化统一在一个函数中?

编辑: 这是我试图让它工作的现实生活场景的简化版本(请原谅我缺乏命名约定和案例不一致):

#include <iostream>
#include <map>

class non_copyable {
public:
    non_copyable() = default;
protected:
    virtual ~non_copyable() = default;
private:
    non_copyable(const non_copyable&) = delete;
    non_copyable& operator=(const non_copyable&) = delete;
};

class InnerValueClass : public non_copyable
{
public:
    InnerValueClass(const int inner_number) : inner_number_(inner_number) {  }
private:
    int inner_number_;
};

class ValueClass : public non_copyable
{
public:
    ValueClass(const int number1) : number1_(number1) {  }
    ValueClass(const bool condition) : condition_(condition), inner_value_(
        std::make_unique<InnerValueClass>(5)) {  }
private:
    int number1_{};
    bool condition_{};
    std::unique_ptr<InnerValueClass> inner_value_{};
};

/* Inline initialization of std::map copies, this is for initialization of non-copy types*/
template <typename TKey, typename TNonCopyableValue>
class make_map_by_moving
{
    typedef std::map<TKey, TNonCopyableValue> map_type;
    map_type map_;
public:
    make_map_by_moving(const TKey& key, TNonCopyableValue&& val)
    {
        map_.emplace(key, std::move(val));
    }
    make_map_by_moving<TKey, TNonCopyableValue>& operator()(const TKey& key, TNonCopyableValue&& val)
    {
        map_.emplace(key, std::move(val));
        return *this;
    }
    operator const map_type&()
    {
        return map_;
    }
};

static std::map<int, ValueClass> map =
        make_map_by_moving<int, ValueClass>
                (1, ValueClass(5))
                (2, ValueClass(true));
/* It goes on like this for hundreds of lines, so I really appreciate any
solution that leave me with a clean initialization rather than calling
functions on std::map */

int main() { }

重复编辑:该问题中提供的解决方案不适用于我的类结构。我也在寻找修复make_map_by_moving 函数的解决方案,换句话说就是内联初始化,答案提供了函数调用的命令式解决方案。

【问题讨论】:

  • 这张地图是否意味着在第一次初始化后可以在运行时编辑?我确实记得一个全局 const 结构的提议(甚至是实现),它提供自然初始化和类似地图的性能
  • 我找到了这个链接:blog.knatten.org/2018/10/05/…,这可能会给你一些见解!

标签: c++ c++17 static-initialization noncopyable


【解决方案1】:

您不能直接执行此操作,因为 initializer_list 的所有元素都有 const 支持 - 并且必须将它们从初始化列表复制到容器中。显然,这需要复制。不幸的是,没有办法从初始化列表中安顿下来。

在 C++17 中,由于保证复制省略,您可以这样做:

std::map<int, non_copyable> get() {
    std::map<int, non_copyable> m;
    m.emplace(std::piecewise_construct, std::tuple(0), std::tuple());
    m.emplace(std::piecewise_construct, std::tuple(1), std::tuple());
    return m;
}

std::map<int, non_copyable> value_classes = get();

此代码对non_copyable 不执行任何复制。我们在map 内放置构造,然后因为get() 是prvalue,没有从get() 复制/移动到value_classesget() 中的m 对象value_classes

稍微有点运动鞋的方法是为此滥用try_emplace()

std::map<int, non_copyable> get() {
    std::map<int, non_copyable> m;
    m.try_emplace(0);
    m.try_emplace(1);
    return m;
}

try_emplace() 自己获取密钥类型(因此您可以只传递一个int),然后是用于单独放置的值的参数,这使得实现这一点的方式更加简洁。

【讨论】:

  • 另一种方法是简单地调用m[0]; m[1];。它将放置默认构造的对象。见en.cppreference.com/w/cpp/container/map/operator_at
  • 为什么是try_emplace 而不是简单的旧emplace
  • 感谢您的回答!我意识到我的问题并不清楚,对此我很抱歉。我不想让这些值稍后定义,我也想定义这些值。我已经用显示我遇到的真实场景的代码更新了我的问题。
  • @U.Bulle 好吧,鉴于InnerValueClass 不可复制,我不知道您希望您的ValueClass(int, const InnerValueClass&amp;) 构造函数如何工作-但除此之外,此答案还提供了提供的途径任意构造函数参数就好了。
【解决方案2】:

我认为您需要在函数中创建带有insert_or_assign 的对象,然后将其返回:

std::map<int, ValueClass> populate()
{
    std::map<int, ValueClass> value_classes;
    value_classes.insert_or_assign(std::make_pair(0, ValueClass());
    return value_classes;
}

你的初始化变成:

std::map<int, ValueClass> value_classes = populate();

但是,这个类有一个虚拟析构函数,这意味着你想要的实际上可能是一个std::map&lt;int, std::unique_ptr&lt;ValueClass&gt;&gt;,而不是实际对象的映射(不确定这些对象将用于什么?)。

问题编辑后编辑:

在这种情况下,Barrys suggestion is the one to follow, usingemplace`:

std::map<int, ValueClass> populate()
{
    std::map<int, ValueClass> value_classes;
    value_classes.emplace(1, 5);
    return value_classes;
}

还包括functional

【讨论】:

  • 感谢您非常快速且内容丰富的回答。我已经用显示真实应用程序的代码更新了我的问题。 insert_or_assign 也没有帮助它编译。
  • 我收到C2660 'std::pair&lt;const _Kty,_Ty&gt;::pair': function does not take 2 arguments 与您对 Barry 建议的整合 :(
  • 我建议你更改你的特殊类以删除 emplace 的使用。 Barry 的代码或我的代码适用于您的案例,只需创建一个实际函数即可。此外,您的代码中有一个大错误,因为 reference_wrapper 指向一个被破坏的临时对象。
  • 你是对的。我编辑了问题并修复了 reference_wrapper,以免分散其他读者的注意力(现在是 unique_ptr)。你的意思是我创建了一个实际的函数?我都试过了,它们都无法编译。使用您的解决方案,我在“insert_or_assign”行上得到 5 个不同的错误(不包括 make_map_by_moving,我在单独的项目中进行了测试)。我用 MSVC 编译 godbolt.org/z/4RhuID
【解决方案3】:

您根本不能使用initializer_listmove 来自non-copyable 对象的对象。

您的班级删除了copy constructorassignment operator。当您尝试使用initializer_list 初始化map 或任何其他container 时,initializer_list 严格强制您引用LValue 并禁止RValue 移动或转发语义。

这是一篇很好的博客文章,解释了所有细节:knatten.org 以及找到的类似 Q/A here

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-02-01
    • 2010-09-13
    • 1970-01-01
    • 1970-01-01
    • 2014-10-14
    • 1970-01-01
    • 2015-05-08
    • 2020-05-18
    相关资源
    最近更新 更多