首页 > 解决方案 > 当类有常量时将对象插入向量

问题描述

假设我有一个只有常量的类,因为值永远不会改变。

struct Test {
    const std::string id;
    const int a;
    const double b;
};

稍后,我想将对象添加到向量中,但我希望向量按b从大到小排序。我使用插入排序,因为只会有一个很小的数字(可能是 5 个)。

std::vector<Test> values;

void addValue( const std::string &id, int a, double b ) {
    // Keep stockpiles sorted by weight (large to small)
    auto itr = values.begin();
    while ( itr != values.end() && itr->b > b ) ++itr;
    values.insert( itr, { id, a, b } );
    // end sort
}

尝试上述操作时,我在尝试插入向量时收到以下错误:

错误:无法分配“Test”类型的对象,因为它的复制赋值运算符被隐式删除

我想继续使用 avector来解决这个问题;但我似乎找不到解决排序问题的方法。我能想到的唯一其他选择是有效地vector不断地重新创建。或者,使用 amulti-set或类似的,然后一旦添加了所有值,我就可以转储到一个数组中。

有没有办法绕过这个限制,仍然使用 avector而不是让所有东西都变成非常量?还是我会被迫改变我的结构,或者先搬进一个临时对象?

编辑:还试图避免使用指针

标签: c++

解决方案


正如评论中已经指出的那样,排序需要在向量中的元素周围移动。并且在元素周围移动通常需要移动分配,这通常需要元素的变异......

有一种方法可以摆脱这种困境:与其为现有对象分配一个新值,不如在现有对象之上创建一个新值。可以通过定义一个复制/移动构造函数来做到这一点,例如:

    Test& operator =(Test&& other)
    {
        this->~Test();
        return *new (this) Test(std::move(other));
    }

这样做只有一个问题:通过[basic.life]/8.3,我们不允许使用任何现有的指针或对原始对象的引用,甚至在这样的赋值之后也不能使用原始对象的名称。您将始终必须使用分配的结果(placement-new 的返回值)或清洗过的指针作为访问对象的唯一方法。由于没有具体说明如何std::sort操作,我们不能依赖它来做。

我们能做的是构建一个这样的包装器(为了便于阅读,省略了 noexcept):

template <typename T>
class const_element
{
    T value;

    const T& laundered_value() const { return *std::launder(&value); }
    T& laundered_value() { return *std::launder(&value); }

public:
    template <typename... Args>
    explicit const_element(Args&&... args) : value { std::forward<Args>(args)... } {}

    const_element(const const_element& e) : value(e.laundered_value()) {}
    const_element(const_element&& e) : value(std::move(e.laundered_value())) {}

    const_element& operator =(const const_element& e)
    {
        laundered_value().~T();
        new (&value) T(e.laundered_value());
        return *this;
    }

    const_element& operator =(const_element&& e)
    {
        laundered_value().~T();
        new (&value) T(std::move(e.laundered_value()));
        return *this;
    }

    ~const_element()
    {
        laundered_value().~T();
    }

    operator const T&() const { return laundered_value(); }
    operator T&() { return laundered_value(); }

    friend bool operator <(const_element& a, const_element& b)
    {
        return a.laundered_value() < b.laundered_value();
    }
};

这样做是包装某种类型的对象,T以上述方式实现复制和移动赋值,并确保对当前值的任何访问始终通过清洗过的指针。

然后我们可以做

    std::vector<const_element<Test>> values;
    values.emplace_back("b", 1, 0.0);
    values.emplace_back("a", 0, 0.0);
    values.emplace_back("c", 2, 0.0);

    std::sort(begin(values), end(values));

这里的工作示例

话虽如此,我建议不要这样做。如果您想要一个无法修改的对象,只需使用 aconst T而不是T仅由 const 成员组成的 a。你不能有 的向量const T。但是你可以有一个向量,T然后只传递一个对 const 向量或一系列 const 元素的引用……


推荐阅读