【发布时间】:2022-01-06 11:06:29
【问题描述】:
问题
我在使用具有递归数据结构的 Boost 序列化库时遇到问题。更准确地说,我想序列化一个矩阵,该矩阵由包含一个值的节点表示,并且每个节点都可以访问其邻居(顶部、底部、左侧、右侧)。为了访问一个节点,每个入口点都存储在一个vector 中(即每行和每列的第一个和最后一个节点)。这是Node 类:
class Node
{
private:
int v;
Node* left;
Node* right;
Node* top;
Node* bottom;
public:
Node() : v(rand() % 100), left(NULL), right(NULL), top(NULL), bottom(NULL)
{
}
//Potential memory leak but that's not the topic
void setLeft(Node* toSet) { left = toSet; }
void setRight(Node* toSet) { right = toSet; }
void setTop(Node* toSet) { top = toSet; }
void setBottom(Node* toSet) { bottom = toSet; }
Node* gLeft() { return left; }
Node* gRight() { return right; }
Node* gTop() { return top; }
Node* gBottom() { return bottom; }
int gValue() { return v; }
template<class Archive>
void serialize(Archive& ar, const unsigned int version)
{
ar& v;
ar& left;
ar& right;
ar& top;
ar& bottom;
}
};
这是Matrix 类,在这个例子中带有generateValues() 函数:
class Matrix
{
private:
int m, n;
std::vector<Node*> firstNodesPerRow;
std::vector<Node*> lastNodesPerRow;
std::vector<Node*> firstNodesPerCol;
std::vector<Node*> lastNodesPerCol;
public:
Matrix(int m, int n) :
m(m), n(n),
firstNodesPerRow(m, NULL), lastNodesPerRow(m,NULL),
firstNodesPerCol(n, NULL),lastNodesPerCol(n, NULL)
{
}
void generateValues()
{
for (int i = 0; i < m; i++)
{
for (int j = 0; j < n; j++)
{
Node* toWrite = new Node();
if (i > 0)
{
toWrite->setTop(lastNodesPerCol.at(j));
lastNodesPerCol.at(j)->setBottom(toWrite);
lastNodesPerCol.at(j) = toWrite;
}
else
{
firstNodesPerCol.at(j) = toWrite;
lastNodesPerCol.at(j) = toWrite;
}
if (j > 0)
{
toWrite->setLeft(lastNodesPerRow.at(i));
lastNodesPerRow.at(i)->setRight(toWrite);
lastNodesPerRow.at(i) = toWrite;
}
else
{
firstNodesPerRow.at(i) = toWrite;
lastNodesPerRow.at(i) = toWrite;
}
}
}
}
template<class Archive>
void serialize(Archive& ar, const unsigned int version)
{
ar& m;
ar& n;
ar& firstNodesPerRow;
ar& firstNodesPerCol;
ar& lastNodesPerRow;
ar& lastNodesPerCol;
}
};
所以我想要实现的是序列化和反序列化Matrix。这是我的main 函数:
#include <cstdlib>
#include <sstream>
#include <vector>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/serialization/vector.hpp>
int main(int argc, char* argv[])
{
int m = 10; int n = 10;
Matrix toSerialize(m,n);
toSerialize.generateValues();
/*
1) Serialize
*/
std::ostringstream oss;
boost::archive::text_oarchive oa(oss);
oa << toSerialize;
std::string serialiazedData = oss.str();
/*
2) Deserialize
*/
Matrix result(m,n);
std::stringstream serializedDataStream(serialiazedData);
boost::archive::text_iarchive ia(serializedDataStream);
ia >> result;
return EXIT_SUCCESS;
}
问题如下:给定一个足够大的值m 或n,main 以stack-overflow 异常结束。我知道它来自Node 的serialize 方法,因为为了序列化一个节点,它需要序列化邻居等等......我发现this topic 这似乎是完全相同的问题。作为起点,答案很有趣,但我很难实现它,因为它不够精确。我的理解是,要解决我的问题,我需要:
- 以迭代的方式对节点进行序列化,这样当涉及到邻居时,对象已经被序列化并且没有
stack-overflow; - 序列化拓扑,在我的例子中通过
Node的指针top,bottom,right,left表示
我在实际实施这个解决方案时遇到了麻烦,因为我能想到的唯一方法是在 serialize 的 serialize 方法中删除 top,bottom,right,left 的序列化 Node,但我可以'没有达到第 2 点?
编辑:我制作了一个矩阵图来帮助阅读。请注意,m x n 矩阵中不一定有 m x n 节点。
编辑 2:我想到的解决方案(不起作用,反序列化时最终导致堆栈溢出)。
//Class Node
template<class Archive>
void serialize(Archive& ar, const unsigned int version)
{
ar& v;
}
//Class Matrix
template<class Archive>
void serialize(Archive& ar, const unsigned int version)
{
ar& m;
ar& n;
for (int i = 0; i < m; i++)
{
Node* current = firstNodesPerRow.at(i);
while (1)
{
if (current == NULL) { break; }
ar& current;
current = current->gRight();
}
}
ar& firstNodesPerRow;
ar& firstNodesPerCol;
ar& lastNodesPerRow;
ar& lastNodesPerCol;
}
解决方案
解决方案的解释在标记为答案的帖子中给出。这是此解决方案的实现:
// class Node
template<class Archive>
void serialize(Archive& ar, const unsigned int version)
{
ar& v;
}
// some buffer struct
struct Neighbors
{
Node* top;
Node* bottom;
Node* left;
Node* right;
template <typename Archive>
void serialize(Archive& ar, const unsigned int version)
{
ar& top;
ar& bottom;
ar& left;
ar& right;
}
};
//class Matrix
template<class Archive>
void save(Archive& ar, const unsigned int version) const
{
std::map<Node*, Neighbors> neighborsPerNode;
for (int i = 0; i < m; i++)
{
Node* current = firstNodesPerRow.at(i);
while (1)
{
if (current == NULL) { break; }
neighborsPerNode[current] = {
current->gTop(),
current->gBottom(),
current->gLeft(),
current->gRight(),
};
current = current->gRight();
}
}
ar& neighborsPerNode;
ar& m;
ar& n;
ar& firstNodesPerRow;
ar& firstNodesPerCol;
ar& lastNodesPerRow;
ar& lastNodesPerCol;
}
template<class Archive>
void load(Archive& ar, const unsigned int version)
{
// Warning ALL the nodes are browsed 2 times :
// 1 - to deserialize neighborsPerNode (below)
// 2 - in the for loop, to create the links between the nodes
std::map<Node*, Neighbors> neighborsPerNode;
ar& neighborsPerNode;
ar& m;
ar& n;
ar& firstNodesPerRow;
ar& firstNodesPerCol;
ar& lastNodesPerRow;
ar& lastNodesPerCol;
for (int i = 0; i < m; i++)
{
Node* current = firstNodesPerRow.at(i);
while (1)
{
if (current == NULL) { break; }
Neighbors myNeighbors = neighborsPerNode.at(current);
current->setTop(myNeighbors.top);
current->setBottom(myNeighbors.bottom);
current->setLeft(myNeighbors.left);
current->setRight(myNeighbors.right);
current = current->gRight();
}
}
}
BOOST_SERIALIZATION_SPLIT_MEMBER()
【问题讨论】:
-
“我在实际实现这个解决方案时遇到了麻烦,因为我能想到的唯一方法是第 1 点意味着删除顶部、底部、右侧、左侧的序列化” - 我认为这是一个错误的假设。你能对照boost.org/doc/libs/1_76_0/libs/serialization/doc/…检查你的想法吗?
-
“按地址跟踪对象”正是我计划使用的机制。问题是,如果我将
top,bottom,right,left留在serialize中Node,那么我需要预先序列化这些指针引用的对象。为此,我可以在Matrix的serialize方法中逐行逐节点浏览矩阵,并在那里序列化每个节点。如果我将top,bottom,right,left留在Node的serialize中,那么第一个节点的序列化将遇到完全相同的问题,因为邻居尚未序列化。我将在 OP 中添加我的代码以使其更清晰。 -
请注意,在 C++ 中您应该使用
nullptr而不是NULL。 -
主要问题是序列化 top,bottom,right,left 会导致归档序列化追逐试图序列化所有内容的指针。
ar& firstNodesPerRow;被序列化的第一个节点将尝试序列化其直接邻居,而那些尝试序列化其直接邻居等...所以在最坏的情况下,您最终会得到n*m的递归调用深度。一个简单的解决方案是仅序列化值并在需要再次加载时重新创建节点结构。 -
我也不太明白如何在示例中省略值。想象一个只有中心元素被设置的 3x3 矩阵。
firstNodesPerRow、lastNodesPerRow等...都只包含零指针,因此无法保存该节点。所以矩阵的中心总会有一个盲点,如果不先填充几个额外的节点,就不能直接添加节点。
标签: c++ recursion serialization boost stack-overflow