c在自制的矢量容器中放置新的
作者:互联网
这里有一些问题非常相似,但它们无法帮助我理解它.
另外,我给出了一个完整的示例代码,因此其他人可能更容易理解.
我已经制作了一个矢量容器(由于内存原因不能使用stl),以前只使用operator = for push_back *,一旦我来到新的位置,我决定向它引入一个额外的“emplace_back”**.
*(T :: operator =预计会处理内存管理)
**(这个名字取自我之后遇到的std :: vector中的类似函数,我给它的原始名称是一团糟).
我读了一些关于使用new new over operator new []的危险的东西,但是无法弄清楚以下是否正常,如果没有,它有什么问题,我该怎么替换它,所以我’我很感激你的帮助.
这是一个简化的代码,没有迭代器,也没有扩展功能,但它指出了这一点:
template <class T>
class myVector {
public :
myVector(int capacity_) {
_capacity = capacity_;
_data = new T[_capacity];
_size = 0;
}
~myVector() {
delete[] _data;
}
bool push_back(T const & t) {
if (_size >= _capacity) { return false; }
_data[_size++] = t;
return true;
}
template <class... Args>
bool emplace_back(Args const & ... args) {
if (_size >= _capacity) { return false; }
_data[_size].~T();
new (&_data[_size++]) T(args...);
return true;
}
T * erase (T * p) {
//assert(/*p is not aligned*/);
if (p < begin() || p >= end()) { return end(); }
if (p == &back()) { --_size; return end(); }
*p = back();
--_size;
return p;
}
// The usual stuff (and more)
int capacity() { return _capacity; }
int size() { return _size; }
T * begin() { return _data; }
T * end() { return _data + _size; }
T const * begin() const { return _data; }
T const * end() const { return _data + _size; }
T & front() { return *begin(); }
T & back() { return *(end() - 1); }
T const & front() const { return *begin(); }
T const & back() const { return *(end() - 1); }
T & operator[] (int i) { return _data[i]; }
T const & operator[] (int i) const { return _data[i]; }
private:
T * _data;
int _capacity;
int _size;
};
谢谢
解决方法:
I read some stuff about the danger of using placement new over
operator new[] but couldn’t figure out if the following is ok or not,
and if not, what’s wrong with it […]
对于operator new []和placement new,如果将两个策略混合在一起,它只是非常糟糕(如通常崩溃类型的未定义行为).
您通常必须做出的主要选择是使用其中一个.如果使用operator new [],则事先为容器的整个容量构造所有元素,并在push_back等方法中覆盖它们.你不会在删除方法中删除它们,例如擦除,只需将它们保留在那里并调整大小,覆盖元素等等.您可以使用operator new []一次性构造和分配多个元素,并使用operator delete []一次性销毁和取消分配它们.
为什么Placement New用于标准容器
首先要理解的是,如果你想要开始滚动你自己的向量或其他符合标准的序列(不仅仅是每个节点有一个元素的链接结构),它们在删除元素时会实际销毁元素,构建元素(不仅仅是在添加时,它将分离为容器分配内存并为其构建元素的想法.所以恰恰相反,在这种情况下,新的安置也不错.实现标准容器的一般品质是必不可少的.但是在这种情况下我们不能将它与operator new []和operator delete []混合使用.
例如,您可以分配内存以保留100个T实例,但您也不希望默认构造它们.您希望在push_back,insert,resize,fill ctor,range ctor,copy ctor等方法中构造它们. – 实际添加元素的方法,而不仅仅是容纳它们的容量.这就是为什么我们需要新的安置.
否则我们失去了std :: vector的通用性,它避免构造不存在的元素,可以在push_backs中复制构造而不是简单地用operator =等覆盖现有的元素.
那么让我们从构造函数开始:
_data = new T[_capacity];
…这将调用所有元素的默认构造函数.我们不希望这样(既不是默认的ctor要求也不是这个费用),因为使用placement new的重点是在已分配内存的位置构造元素,并且这已经构建了所有元素.否则,任何在任何地方使用placement的尝试都会尝试第二次构造一个已构造的元素,并且将是UB.
相反,你想要这样的东西:
_data = static_cast<T*>(malloc(_capacity * sizeof(T)));
这只是给了我们一大块字节.
第二,对于push_back,你正在做:
_data[_size++] = t;
那是在尝试使用赋值运算符,并且在我们之前的修改之后,还使用了尚未构造的未初始化/无效元素.所以我们想要:
new(_data + _size) T(t);
++size;
…这使得它使用复制构造函数.它使它与push_back实际应该做的事情相匹配:在序列中创建新元素而不是简单地覆盖现有元素.
如果要处理容器中间的删除,即使在基本逻辑级别,擦除方法也需要一些工作.但是从资源管理的角度来看,如果使用placement new,则需要手动为已删除的元素调用析构函数.例如:
if (p == &back()) { --_size; return end(); }
……应该更像:
if (p == &back())
{
--size;
(_data + _size)->~T();
return end();
}
你的emplace_back手动调用析构函数但它不应该这样做. emplace_back应该只添加,而不是删除(和销毁)现有元素.它应该与push_back非常相似,但只是调用move ctor.
你的析构函数这样做:
~myVector() {
delete[] _data;
}
但是,当我们采用这种方法时,那就是UB.我们想要更像的东西:
~myVector() {
for (int j=0; j < _size; ++j)
(_data + j)->~T();
free(_data);
}
还有更多的内容可以涵盖异常安全,这是一个完全不同的蠕虫.
但是,这应该让您开始关于在数据结构中针对某些内存分配器(在此示例性情况下为malloc / free)正确使用placement new.
最后但并非最不重要的:
(couldn’t use stl for memory reasons)
……这可能是一个不寻常的原因.您的实现不一定使用比预先调用保留的向量少的内存来为其提供适当的容量.您可以选择32位积分并且无需存储分配器,从而在每个容器级别(不是在每个元素级别)上减少几个字节,但是它可以节省很少的内存换来一大堆工作.
这种事情可以是一个有用的学习练习,虽然可以帮助你以更符合标准的方式构建标准之外的一些数据结构(例如:我发现非常有用的展开列表).
为了ABI的原因,我最终不得不重新发明一些矢量和类似矢量的容器(我们想要一个容器,我们可以通过我们的API保证具有相同的ABI,无论使用什么编译器来构建插件).即便如此,我还是更喜欢使用std :: vector.
请注意,如果您只想控制向量分配内存的方式,可以通过使用兼容接口指定自己的分配器来实现.这可能很有用,例如,如果您想要一个分配128位对齐内存的向量,以便与使用SIMD的对齐移动指令一起使用.
标签:placement-new,c 来源: https://codeday.me/bug/20190824/1707237.html