有两个关于替换不可分配的向量元素的问题:
对象不可分配的典型原因是其类定义包含const
个成员,因此删除了operator=
。
std::vector
要求其元素类型可分配。事实上,至少使用GCC,当对象不可分配时,直接赋值(vec[i] = x;
),以及erase()
和insert()
的组合来替换元素都不起作用。
可以使用以下函数(使用vector::data()
,直接元素销毁和使用复制构造函数new)来替换元素而不会导致未定义的行为吗?
template <typename T>
inline void replace(std::vector<T> &vec, const size_t pos, const T& src)
{
T *p = vec.data() + pos;
p->~T();
new (p) T(src);
}
下面是一个使用函数的例子。这在GCC 4.7中编译并且似乎有效。
struct A
{
const int _i;
A(const int &i):_i(i) {}
};
int main() {
std::vector<A> vec;
A c1(1);
A c2(2);
vec.push_back(c1);
std::cout << vec[0]._i << std::endl;
/* To replace the element in the vector
we cannot use this: */
//vec[0] = c2;
/* Nor this: */
//vec.erase(begin(vec));
//vec.insert(begin(vec),c2);
/* But this we can: */
replace(vec,0,c2);
std::cout << vec[0]._i << std::endl;
return 0;
}
答案 0 :(得分:14)
这是非法的,因为3.8p7描述了使用析构函数调用和placement new来重新创建一个对象,它指定了对数据成员类型的限制:
3.8对象生命期[basic.life]
7 - 如果在对象的生命周期结束之后并且在重用或释放对象占用的存储之前,在原始对象占用的存储位置创建一个新对象,指向原始对象的指针对象[...]可以 用于操纵新物体,如果:[...]
- 原始对象的类型[...]不包含任何非静态数据成员,其类型为const-qualified 或引用类型[...]
因此,由于你的对象包含一个const数据成员,所以在析构函数调用和放置之后,当用于引用第一个元素时,向量的内部data
指针变为无效;我认为任何明智的阅读都会得出结论,这同样适用于其他因素。
这样做的理由是,优化者有权假设const和引用数据成员未被分别修改或重新安置:
struct A { const int i; int &j; };
int foo() {
int x = 5;
std::vector<A> v{{4, x}};
bar(v); // opaque
return v[0].i + v[0].j; // optimised to `return 9;`
}
答案 1 :(得分:2)
@ ecatmur的回答在撰写本文时是正确的。在C ++ 17中,我们现在得到std::launder
(wg21 proposal P0137)。添加此内容是为了使std::optional
之类的内容与const
成员一起使用。只要你记得launder
(即清理)你的内存访问,那么这将在不调用未定义行为的情况下工作。