我在编写C ++ HashTable的Copy构造函数时遇到问题。现在下面是类结构
template <class TYPE>
class HashTable : public Table<TYPE>
{
struct Record
{
TYPE data_;
string key_;
Record* Next;
Record(const string& key, const TYPE& data)
{
key_ = key;
data_ = data;
Next = nullptr;
}
Record(const Record& a) {
if(!a.key_.empty()){
if(a.Next == nullptr){
Next = nullptr;
}
else
{
Record* temp = a.Next ;
Record *temp2 = Next;
while(temp != nullptr)
{
temp2 = temp ;
temp = temp->Next ;
}
temp2->Next = nullptr;
}
data_ = a.data_ ;
key_ = a.data_ ;
} // user-
};
int TableSize;
Record** records;
}
};
以下是复制构造函数 模板
HashTable<TYPE>::HashTable(const HashTable<TYPE>& other)
{
records = new Record*[other.TableSize];
TableSize = other.TableSize;
for(int i = 0 ; i < other.TableSize; i++)
records[i]= (new Record(*other.records[i]));
}
我还在ideone http://ideone.com/PocMTD上发布了代码。复制构造函数的代码似乎崩溃了。我没有看到任何会导致程序崩溃的内存泄漏。我尝试过memcopy,使用insert函数,似乎都失败了。
答案 0 :(得分:2)
将int TableSize;
和Record** records;
替换为std::vector<std::unique_ptr<Record>>
在Record
中,将Record* Next;
更改为Record* Next=nullptr;
。
停止致电new
。
包括HashTable(HashTable&&)=default;
。
HashTable<TYPE>::HashTable(const HashTable<TYPE>& other)
{
records.reserve( other.records.size() );
for (auto const& rec_in : other.records)
records.emplace_back( new Record(*rec_in) ); // make_shared<Record> in C++14
}
现在我们不再进行手动内存管理了。所以一整套担忧都消失了。
接下来,查看原始Next
指针。这是坏消息。复制Record
时,Next
指针指向旧的Record
结构集。
我们可以通过几种方式解决这个问题。最简单的方法是使用偏移指针。
template<class T>
struct offset_ptr {
std::ptrdiff_t offset = std::numeric_limits<std::ptrdiff_t>::max();
explicit operator bool()const {
return offset!=std::numeric_limits<std::ptrdiff_t>::max();
}
T* get() const {
return (T*)( offset+(char*)this );
}
T* operator->() const { return get(); }
T& operator*() const { return *get(); }
operator T*() const { return get(); }
offset_ptr(std::nullptr_t):offset_ptr() {}
explicit offset_ptr(T* p) {
if (!p) return;
offset = (char*)p-(char*)this;
Assert(*this);
}
offset_ptr()=default;
offset_ptr(offset_ptr const&)=default;
offset_ptr& operator=(offset_ptr const&)=default;
offset_ptr(offset_ptr&&)=default;
offset_ptr& operator=(offset_ptr&&)=default;
};
而不是按绝对位置存储指针,而是存储偏移量。
现在我们这样做:
template<class TYPE> struct Table{};
template <class TYPE>
class HashTable :public Table<TYPE>
{
public:
struct Record
{
TYPE data_;
std::string key_;
offset_ptr<Record> Next;
Record(const std::string& key, const TYPE& data)
{
key_ = key;
data_ = data;
Next = nullptr;
}
Record(const Record& a)
{
if(!a.key_.empty())
{
if(a.Next == nullptr)
{
Next = nullptr;
}
else
{
auto temp = a.Next;
while(temp != nullptr)
{
Next = temp;
temp = temp->Next;
}
}
data_ = a.data_;
key_ = a.data_;
}
}
};
std::vector<Record> records;
};
并且不需要复制ctor;偏移量ptr将另一个记录的位置知道为记录中的偏移量。数据按值而不是按引用存储。
请注意,我们有vector
个Record
,而不是指向Record
的指针。这是offset_ptr
工作的关键。调整大小不是问题,因为偏移量保持不变。复制仍然是安全的,因为每一侧的偏移现在都指向其矢量中的其他元素。在中间插入/移除是危险的,但简单地归零元素不是。
请注意,上述std::ptrdiff_t
不支持大小为offset_ptr
或更高的缓冲区。在一个大约2演出的64位系统上;在64位系统上它很大。 (我不使用0作为空值,因为如果我这样做,offset_ptr<X>
作为struct X
的第一个成员,如果我指向其封闭的X
将会无效。)
boost
也有一个较少的定制offset_ptr
类型。上面的实现是一个简单的草图,而不是一个可靠的实现。
答案 1 :(得分:0)
你没有在这里显示完整的代码(也不是在ideone上),但让我根据我所看到的情况进行猜测。
other
对象具有完整设置的Record
s列表。HashTable
类有一个析构函数(未显示),它删除了所有链接的Record
。Record
的副本c'tor(对于指向Record
的指针数组中的每个条目)。 Record
coyp c'tor仅生成浅拷贝,即仅复制指向下一个元素的指针(它仍将指向other
哈希表中复制的记录的下一个元素。 / LI>
因此,当other
及其副本被删除时(在范围或程序的末尾;未显示),您将有双重删除(崩溃)。
修复:确保Record
具有正确的复制构造函数,复制赋值和析构函数(甚至可以移动c'tor并移动赋值)(规则为5)。
这同样适用于HashTable
类。
更好的修复:使用std::unordered_map
。