用统一的内联初始化方式初始化具有不可复制值的静态std::map

11

我想初始化一个静态的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函数,以实现初始化而不进行复制,但是我失败了。我尝试了这个那个其他的,但是它们都无法在Visual Studio 15.9.4中编译。

如何初始化静态std::map,其中不强制进行复制,并且在一个函数中统一初始化,使用Visual Studio编译器?

编辑: 这里是实际情况的简化版本,我正在尝试让它工作(对于缺乏命名规范和大小写不一致,请原谅):

#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函数,换句话说是内联初始化,那里提供的答案是带有函数调用的命令式解决方案。

这个地图在第一次初始化后是否可以在运行时进行编辑?我记得有一个提案(甚至是实现),提供了自然初始化和类似映射的性能的全局常量结构。 - M.M
我找到了这个链接:https://blog.knatten.org/2018/10/05/why-you-cant-list-initialize-containers-of-non-copyable-types/,它可能会给你一些启示! - Francis Cugler
3个回答

7
您不能直接这样做,因为initializer_list的所有元素都有const备份 - 它们必须从初始化列表复制到容器中。显然,这需要复制。遗憾的是,没有办法从初始化列表中进行emplace。
在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内进行就地构造(emplace construct),由于get()是一个prvalue,因此从get()value_classes没有任何复制/移动。在get()中的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;
}
< p > try_emplace() 函数会单独接受键类型(所以你只需传递一个 int),然后分别接受用于插入值的参数,从而更加简洁地实现此操作。


另一种方法是直接调用 m[0]; m[1];。这将插入默认构造的对象。请参阅 https://en.cppreference.com/w/cpp/container/map/operator_at。 - tmlen
为什么要使用 try_emplace 而不是简单的旧式 emplace - R2RT
谢谢您的回答!我意识到我在问题上表述不够清楚,对此我感到抱歉。我不想将值留待以后定义,我希望能够定义这些值。我已经更新了我的问题,并附上了展示我所面临实际情况的代码。 - U. Bulle
@U.Bulle 嗯,我不知道你如何期望ValueClass(int, const InnerValueClass&)构造函数工作,因为InnerValueClass是不可复制的 - 但除此之外,这个答案提供了一条路径来提供任意构造函数参数。 - Barry

4

我认为你需要在一个函数中使用 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<int, std::unique_ptr<ValueClass>>而不是实际对象的映射(不确定这些对象将用于什么?)。
在问题编辑后进行编辑:
在这种情况下,Barry的建议是使用`emplace`:
std::map<int, ValueClass> populate()
{
    std::map<int, ValueClass> value_classes;
    value_classes.emplace(1, 5);
    return value_classes;
}

还包括功能性


非常感谢您快速而详细的回答。我已经更新了我的问题,并附上了展示实际应用的代码。insert_or_assign也没有帮助它编译。 - U. Bulle
使用Barry的建议整合之后,我遇到了C2660 'std::pair<const _Kty,_Ty>::pair': function does not take 2 arguments错误。 - U. Bulle
我建议您更改特殊类以删除使用emplace的用法。Barry的代码或我的代码都适用于您的情况,只需创建一个实际的函数即可。此外,您的代码存在一个严重的错误,因为reference_wrapper指向一个将被销毁的临时对象。 - Matthieu Brucher
你说得对。我编辑了问题并将 reference_wrapper 更改为 unique_ptr,以免干扰其他读者。您说的是我要创建一个实际的函数吗?我尝试了两种方法,但它们都无法编译。使用您的解决方案,在“insert_or_assign”行上我获得了5个不同的错误(make_map_by_moving未包含在内,我在单独的项目中进行了测试)。我使用MSVC进行编译。https://godbolt.org/z/4RhuID - U. Bulle

3

如果一个对象是非可复制(non-copyable)的,就不能使用initializer_list对它进行移动(move)。

当你的类删除了copy constructorassignment operator时,如果你试图用initializer_list来初始化你的map或者其他containerinitializer_list严格要求你引用一个LValue,禁止使用RValue 的移动(move)或向前(forward)语义。

这里有一篇非常好的博客文章详细解释了这些细节:knatten.org,还有一个类似的问题的问答在这里


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