最小的 C++ STL 向量实现问题
Posted
技术标签:
【中文标题】最小的 C++ STL 向量实现问题【英文标题】:Minimal C++ STL Vector Implementation Problems 【发布时间】:2011-10-13 11:20:42 【问题描述】:我遇到了一个技术问题,这真的让我很困惑。我提前道歉,因为我可能没有提供相关细节;我还不知道为什么会出错,而且包含我正在使用的所有代码是过分的。
我正在处理一个使用 C++ STL 的大型程序。我正在将此代码移动到一个非常敏感的环境,没有标准的 clib 或 STL 实现;它将重新定义 malloc/free/new/delete 等...为此,我需要用我自己的简化实现替换 std:: 部分。我从 std::vector 开始。现在它在标准生态系统中运行,所以它是 GNU libc 和 STL。唯一改变的是这个向量类。
当我使用替换的类执行程序时,它会出现段错误。我已经通过 GDB 进行了处理,发现程序将使用下标运算符从向量中请求一个对象。当对象引用被返回时,一个方法被调用并且程序段错误。似乎找不到此方法并最终出现在 GDB 的 main_arena() 中。对象的类型是继承类。
我真的完全不确定这里有什么问题。我很想提供更多细节,但我不确定我还能提供什么。我只能假设我的向量实现有问题,因为程序中没有任何其他内容被更改。也许有一些明显的事情我在这里做错了,我根本没有看到。
我正在使用:g++ (GCC) 4.4.5 20110214 (Red Hat 4.4.5-6)
非常感谢任何反馈/建议!
#ifndef _MYSTL_VECTOR_H_
#define _MYSTL_VECTOR_H_
#include <stdlib.h>
#include <assert.h>
typedef unsigned int uint;
namespace mystl
/******************
VECTOR
********************/
template <typename T>
class vector
private:
uint _size;
uint _reserved;
T *storage;
void init_vector(uint reserve)
if (reserve == 0)
_reserved = 0;
return;
storage = (T*)malloc(sizeof(T)*reserve);
assert(storage);
_reserved = reserve;
public:
vector()
// std::cerr << "default constructor " << this << std::endl;
storage = NULL;
_size = 0;
_reserved = 0;
vector(const vector<T> &other)
// std::cerr << "copy constructor " << this << std::endl;
storage = NULL;
_size = 0;
_reserved = 0;
init_vector(other.size());
_size = other.size();
for (uint i=0; i<other.size(); i++)
storage[i] = T(other[i]);
vector(uint init_num, const T& init_value)
// std::cerr << "special constructor1 " << this << std::endl;
storage = NULL;
_size = 0;
_reserved = 0;
init_vector(init_num);
for (size_t i=0; i<init_num; i++)
push_back(init_value);
vector(uint init_num)
// std::cerr << "special constructor2 " << this << std::endl;
storage = NULL;
_size = 0;
_reserved = 0;
init_vector(init_num);
void reserve(uint new_size)
if (new_size > _reserved)
storage = (T*)realloc(storage, sizeof(T)*new_size);
assert(storage);
_reserved = new_size;
void push_back(const T &item)
if (_size >= _reserved)
if (_reserved == 0) _reserved=1;
reserve(_reserved*2);
storage[_size] = T(item);
_size++;
uint size() const
return _size;
~vector()
if (_reserved)
free(storage);
storage = NULL;
_reserved = 0;
_size = 0;
// this is for read only
const T& operator[] (unsigned i) const
// do bounds check...
if (i >= _size || i < 0)
assert(false);
return storage[i];
T& operator[] (unsigned i)
// do bounds check...
if (i >= _size || i < 0)
assert(false);
return storage[i];
// overload = operator
const vector<T>& operator= (const vector<T>& x)
// check for self
if (this != &x)
_reserved = 0;
_size = 0;
storage = NULL;
init_vector( x.size() );
for(uint i=0; i<x.size(); i++)
storage[i] = T(x[i]);
_size = x.size();
return *this;
uint begin() const
return 0;
void insert(uint pos, const T& value)
push_back(value);
if (size() == 1)
return;
for (size_t i=size()-2; i>=pos&& i>=0 ; i--)
storage[i+1] = storage[i];
storage[pos] = value;
void erase(uint erase_index)
if (erase_index >= _size)
return;
//scoot everyone down by one
for (uint i=erase_index; i<_size; i++)
storage[i] = storage[i+1];
_size--;
void erase(uint start, uint end)
if (start > end)
assert(false);
if (end > _size)
end = _size;
for (uint i=start; i<end; i++)
erase(start);
assert(false);
void clear()
erase(0,_size);
bool empty() const
return _size == 0;
; //class vector
#endif // _MYSTL_VECTOR_H_
【问题讨论】:
你能给我们一个简单的main()
来复制段错误吗?
这看起来躲不过if (i >= _size || i < 0)
,unsigned i
怎么会小于0?
不幸的是,我无法用最小化的代码复制故障。我想在我创建对象、将它们存储在向量中并再次访问它们之间发生了一些事情。我再玩一会儿。
【参考方案1】:
哇!
您的赋值运算符也会泄漏内存。
因为你正在使用 malloc/release 构造函数到你的类型 T 将不会被调用,因此你不能将你的向量用于除了最微不足道的对象之外的任何东西。
编辑:
今天早上我有点无聊:试试这个
#include <stdlib.h> // For NULL
#include <new> // Because you need placement new
// Because you are avoiding std::
// An implementation of swap
template<typename T>
void swap(T& lhs,T& rhs)
T tmp = lhs;
lhs = rhs;
rhs = tmp;
template <typename T>
class vector
private:
unsigned int dataSize;
unsigned int reserved;
T* data;
public:
~vector()
for(unsigned int loop = 0; loop < dataSize; ++loop)
// Because we use placement new we must explicitly destroy all members.
data[loop].~T();
free(data);
vector()
: dataSize(0)
, reserved(10)
, data(NULL)
reserve(reserved);
vector(const vector<T> &other)
: dataSize(0)
, reserved(other.dataSize)
, data(NULL)
reserve(reserved);
dataSize = reserved;
for(unsigned int loop;loop < dataSize;++loop)
// Because we are using malloc/free
// We need to use placement new to add items to the data
// This way they are constructed in place
new (&data[loop]) T(other.data[loop]);
vector(unsigned int init_num)
: dataSize(0)
, reserved(init_num)
, data(NULL)
reserve(reserved);
dataSize = reserved;
for(unsigned int loop;loop < dataSize;++loop)
// See above
new (&data[loop]) T();
const vector<T>& operator= (vector<T> x)
// use copy and swap idiom.
// Note the pass by value to initiate the copy
swap(dataSize, x.dataSize);
swap(reserved, x.rserved);
swap(data, x.data);
return *this;
void reserve(unsigned int new_size)
if (new_size < reserved)
return;
T* newData = (T*)malloc(sizeof(T) * new_size);
if (!newData)
throw int(2);
for(unsigned int loop = 0; loop < dataSize; ++loop)
// Use placement new to copy the data
new (&newData[loop]) T(data[loop]);
swap(data, newData);
reserved = new_size;
for(unsigned int loop = 0; loop < dataSize; ++loop)
// Call the destructor on old data before freeing the container.
// Remember we just did a swap.
newData[loop].~T();
free(newData);
void push_back(const T &item)
if (dataSize == reserved)
reserve(reserved * 2);
// Place the item in the container
new (&data[dataSize++]) T(item);
unsigned int size() const return dataSize;
bool empty() const return dataSize == 0;
// Operator[] should NOT check the value of i
// Add a method called at() that does check i
const T& operator[] (unsigned i) const return data[i];
T& operator[] (unsigned i) return data[i];
void insert(unsigned int pos, const T& value)
if (pos >= dataSize) throw int(1);
if (dataSize == reserved)
reserve(reserved * 2);
// Move the last item (which needs to be constructed correctly)
if (dataSize != 0)
new (&data[dataSize]) T(data[dataSize-1]);
for(unsigned int loop = dataSize - 1; loop > pos; --loop)
data[loop] = data[loop-1];
++dataSize;
// All items have been moved up.
// Put value in its place
data[pos] = value;
void clear() erase(0, dataSize);
void erase(unsigned int erase_index) erase(erase_index,erase_index+1);
void erase(unsigned int start, unsigned int end) /* end NOT inclusive so => [start, end) */
if (end > dataSize)
end = dataSize;
if (start > end)
start = end;
unsigned int dst = start;
unsigned int src = end;
for(;(src < dataSize) && (dst < end);++dst, ++src)
// Move Elements down;
data[dst] = data[src];
unsigned int count = start - end;
for(;count != 0; --count)
// Remove old Elements
--dataSize;
// Remember we need to manually call the destructor
data[dataSize].~T();
unsigned int begin() const return 0;
; //class vector
【讨论】:
您好,感谢您的反馈。你能详细说明一下擦除问题吗? @Bob,请仔细查看循环条件,尤其是。结束条件检查。 @Bob: 和断言。以及擦除的复杂性。目前是 O(n^2) 也许我错过了什么。您是说擦除执行不力还是语义错误?我同意前者,但我没有看到后者。看起来很奇怪,第二次擦除用擦除(开始)调用第一次擦除,并且开始是一个常量变量。当考虑到第一次擦除的语义时,这是有道理的。 @Bob:不,我想制作 RHS 的副本,这样我就可以使用交换了。这是实现赋值运算符的标准方式。请查收Copy and Swap Idiom
【参考方案2】:
根据您当前的内存处理,此向量仅适用于普通的旧数据类型。
要处理所有类型,必须确保对象
实际上是创建的(malloc 不这样做), 已销毁(免费不这样做), 并且您不能使用 realloc 重新分配内存,因为如果将复杂对象按字节复制到另一个位置,则不能保证它们保持有效。【讨论】:
【参考方案3】:看起来可以在您的问题中找到答案:“当返回对象引用时,会调用一个方法并且程序会出现段错误。它似乎找不到该方法并最终出现在 GDB 中的 main_arena() 中。对象的类型是继承类。”
您可能将基类实例 T 存储在向量中,但是为从 T 继承的类的实例创建 push_back。在 push_back storage[_size] = T(item); 中,您进行了强制转换(实际上是创建复制构造函数 T: T(const T&)) 项到 T (这可能命名为“类型切割”),然后获取对 T 的引用并使用 T 的虚拟表调用从 T 继承的类的方法,其中该方法尚未定义/抽象。我对吗? 为了使其正常工作,您应该将 T* 放入向量或 shared_ptr/unique_ptr 中,具体取决于您应用于向量元素的所有权条款。 通常在向量中只能存储 POD(Plain Old Data)类型。
【讨论】:
希望三年半后这仍然有用:) 许多序列容器用户不了解 C++ 标准,“奇怪”错误成倍增加。 作为我上面评论的补充:C++ ISO 标准文本:“23.2.3 序列容器序列容器组织了一组有限的对象,所有对象的类型都相同,......” object 在构造时被定义为最派生的对象构造函数调用。即使它被强制转换为基类,它仍然有自己的类型。但是当类型切割发生时,结果对象的类型被更改为基类型,并且类型变得与它被强制转换的基类相同。以上是关于最小的 C++ STL 向量实现问题的主要内容,如果未能解决你的问题,请参考以下文章
在 C++ 中为我自己的自定义向量模板类实现迭代器(类似于 STL)[重复]
修改 C++ 算法 STL 中的 make_heap 以作为最小堆工作
c++ stl里的向量vector非常好用,那么它是怎么实现的呢