当类具有常量时,将对象插入到向量中

Insert Objects into Vector when Class Has Constants

本文关键字:插入 对象 向量 常量      更新时间:2023-10-16

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

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"的对象,因为其复制赋值运算符被隐式删除

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

有没有办法绕过这个限制,仍然使用vector并且不使所有内容都非常常量?还是我会被迫改变我的结构,或者先搬进一个临时对象?

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

正如注释中已经指出的那样,排序需要移动向量中的元素。而移动元素通常需要移动分配,这通常需要元素的突变......

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

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

这只有一个问题:通过 [basic.life]/8.3,我们不允许使用任何现有的指针或对原始对象的引用,甚至不允许在这样的赋值后使用原始对象的名称。您将始终必须使用赋值的结果(放置-new 的返回值(或已清洗的指针作为访问对象的唯一方法。由于没有指定std::sort如何准确操作,因此我们依赖它来执行此操作。

我们可以做的是构建这样的包装器(除了省略可读性(:

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));

工作示例在这里

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

您可以在向量中存储指针。当然,您还需要清理所有内容。http://cpp.sh/3h7dr 的例子

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;
Test* t = new Test{id,a,b};
values.insert( itr, t );
// end sort
}