循环遍历 C++ 中 class 中包含的向量而不复制影响值?
Loop through a vector contained within a class in C++ without copying affecting values?
我正在尝试编写一种算法来检查图形是否已连接(构建一个棋盘游戏,其中将地图包含为图形,其中区域是顶点,边界是边)。
每个区域都包含一个区域向量,这些区域是它的邻居(向量邻居)。
我构建地图并检查它是否在此处的 main() 函数中连接:
int main()
{
Map map;
Region r1("R1");
Region r2("R2");
Region r3("R3");
r1.addNeighbor(r2);
r2.addNeighbor(r1);
r2.addNeighbor(r3);
r3.addNeighbor(r2);
map.addRegion(r1);
map.addRegion(r2);
map.addRegion(r3);
map.traversal(r1);
map.isConnected();
return 0;
}
这是我的 traversal() 和 isConnected() 方法实现:
void Map::traversal(Region currentNode)
{
visited.push_back(currentNode.getRegionName());
Region* current = ¤tNode;
cout << (*current).getRegionName() << " loc: " << current << endl;
for (auto const & neighbor : (currentNode).getNeighbors())
{
if (std::find(visited.begin(), visited.end(), neighbor.getRegionName()) != visited.end()) {
}
else {
cout << (neighbor).getRegionName() << " neighbors: " << (neighbor).getNeighbors().size() << " location: " << &(neighbor) << endl;
traversal(neighbor);
}
}
}
bool Map::isConnected()
{
cout << visited.size() << endl;
cout << regions.size() << endl;
vector<string> regionList;
for (int i = 0; i < regions.size(); i++)
{
regionList.push_back(regions[i].getRegionName());
}
if (visited.size() == regionList.size())
{
return true;
}
else
{
return false;
}
}
我这里遇到的问题是,由于某种原因,当我在遍历函数递归过程中获取起始节点以外的节点的邻居时,由于某种原因,该函数有时不再记住当前节点的邻居正在遍历的节点((neighbor).getNeighbors().size() 的输出有时会等于 0)。另外,currentNode的地址并不总是与被引用的原始对象的地址相同,导致我认为它是复制对象而不是直接指向其内存位置。
如有任何帮助,我们将不胜感激。我对 C++ 和指针的概念还很陌生。
根据要求,这是我所在地区 class 的代码:
Region.h
#pragma once
#include <string>
#include <vector>
using namespace std;
class Region
{
private:
string owner;
string regionName;
int numTokens;
public:
vector<Region> neighbors;
void setOwner(string playerName);
void setRegionName(string name);
void setNumTokens(int num);
void addNeighbor(Region r);
vector<Region> getNeighbors() const;
string getOwner() const;
string getRegionName() const;
int getNumTokens() const;
Region();
Region(string regionName);
~Region();
};
Region.cpp
#include "stdafx.h"
#include "Region.h"
Region::Region()
{
}
Region::Region(string name)
{
regionName = name;
owner = "none";
numTokens = 0;
}
Region::~Region()
{
}
void Region::setOwner(string playerName)
{
playerName = owner;
}
string Region::getRegionName() const
{
return regionName;
}
int Region::getNumTokens() const
{
return numTokens;
}
void Region::setRegionName(string name)
{
regionName = name;
}
void Region::setNumTokens(int num)
{
numTokens = num;
}
void Region::addNeighbor(Region r)
{
neighbors.push_back(r);
}
vector<Region> Region::getNeighbors() const
{
return neighbors;
}
string Region::getOwner() const
{
return owner;
}
在
void Map::traversal(Region currentNode)
currentNode
是按值传递的。这意味着 currentNode
独立于调用 traversal
时作为参数提供的 Region
的副本(任何重要的地方)。这是您用
记录的不同地址
cout << (*current).getRegionName() << " loc: " << current << endl;
用
修复
void Map::traversal(Region & currentNode)
虽然
void Map::traversal(const Region & currentNode)
如果您不打算在函数内部(或作为函数的结果)更改 currentNode
,则首选 。它可以防止错误,并且由于您承诺不更改提供的 Region
,编译器可以利用一些技巧和优化。
下一个陷阱是
vector<Region> neighbors;
存储其中放置的任何内容的副本,而不是原件。所以
r1.addNeighbor(r2);
来电
void Region::addNeighbor(Region r)
也是按值传递(r
是 r2
的副本)和
neighbors.push_back(r);
将 r
的副本放入向量中。最终结果是r1
并不真正知道r2
,它知道一个副本。复制后修改r2
不影响复制。你被困住了。您必须存储指针。
区域需要看起来更像
class Region
{
private:
string owner;
string regionName;
int numTokens;
public:
vector<Region *> neighbors; // change here
void setOwner(string playerName);
void setRegionName(string name);
void setNumTokens(int num);
void addNeighbor(Region * r); // change here
vector<Region *> getNeighbors() const; // change here
string getOwner() const;
string getRegionName() const;
int getNumTokens() const;
Region();
Region(string regionName);
// ~Region(); not required.
};
不相关:Region
不包含非自我管理的资源,因此可以利用 The Rule of Zero。您可以安全地删除它的析构函数。是的。我知道它包含 vector
个原始指针。更多内容请见下文。
这可能会让您陷入内存管理的噩梦,因此您必须确保您拥有那些 Region
每个人都指指点点的东西的所有权。
所有权基本"Who's responsible for the clean-up? Who makes sure what's being pointed at is freed when its no longer required?"
对于您的示例,
Region r1("R1");
是一个自动变量。它管理自己的生命周期。它在超出范围时被释放。你可以
r1.addNeighbor(&r2); //pass raw pointer to r2
和 r2
将在 r1
之前按计划销毁,如果你考虑一下这有点危险。 r1
仍然持有指向 r2
的指针,但 r1
也超出了范围,因此您需要在析构函数中做一些愚蠢的事情或进入多线程。 Region
不需要析构函数,所以你很安全,如果你是多线程的,则需要一组全新的假设,比如为什么你在 main
中超出范围,而你仍然有线程 运行?
但是,如果您要添加和删除 Region
并动态重塑图表,那又如何呢?
这很快就会变得丑陋。
人们通常会选择在 neighbors
中使用智能指针来管理
对他们的记忆。在你的情况下效果不佳。在很多情况下也不适合他们。
vector<unique_ptr<Region>>
(唯一指针)没有任何意义,因为可以有很多 Region
都指向同一个 Region
。如此独特。
shared_ptr
也没有意义,因为 r1
指向 r2
而 r2
指向 r1
。必须采取直接行动来消除循环,这在很大程度上违背了智能指针的要点。如果您忘记或因异常而出轨怎么办?
有可以玩的游戏weak_ptr
,但在双向图中谁是共享的,谁是弱的?
意见警告:我赞成 Region
s 使用原始指针和 Regions
的主列表(再次是 vector<unique_ptr<Region>>
,但这次有效)作为图的成员 class 管理访问、插入、删除和所有其他操作的方式与管理链接节点的链表相同。
可能有我没有看到的更清晰的解决方案。
编辑:这是基于 M.M. 的评论
class Region
{
private:
string owner;
string regionName;
int numTokens;
public:
vector<string> neighbors; // change here. If you have large numbers of neighbours
// consider using a std::set in place of the vector
void setOwner(string playerName);
void setRegionName(string name);
void setNumTokens(int num);
void addNeighbor(const string & name); // change here. Accepting const reference
// reduces unnecessary copying. May want
// to use the same trick for other methods
// receiving a string
const vector<string> & getNeighbors() const; // change here. Returning const
// reference reduces unnecessary copying
string getOwner() const;
string getRegionName() const;
int getNumTokens() const;
Region();
Region(string regionName);
// ~Region(); not required.
};
这假定 regionName
是唯一的,并将其用作从类似于 map<string, Region> masterlist;
的主列表访问 Region
的密钥。 masterlist
管理您所有 Regions
的存储空间。从中删除一个 Region
如果在 masterlist
中找不到 regionName
你不必担心无效指针,你只需记下并从 neighbors
中删除它.
记得小心使用下标运算符。在masterlist[name]
中如果找不到name
,则默认为其构造一个Region
并存储。如果您正在寻找应该存在的 Region
,请首选使用 find
方法。
如果您有固定数量的区域,请考虑使用简单数组或 std::array
代替 masterlist
的 map
并使用 [=24= 的索引] 在数组中作为标识符代替 string
。
补充阅读:What's the difference between passing by reference vs. passing by value?
我正在尝试编写一种算法来检查图形是否已连接(构建一个棋盘游戏,其中将地图包含为图形,其中区域是顶点,边界是边)。
每个区域都包含一个区域向量,这些区域是它的邻居(向量邻居)。
我构建地图并检查它是否在此处的 main() 函数中连接:
int main()
{
Map map;
Region r1("R1");
Region r2("R2");
Region r3("R3");
r1.addNeighbor(r2);
r2.addNeighbor(r1);
r2.addNeighbor(r3);
r3.addNeighbor(r2);
map.addRegion(r1);
map.addRegion(r2);
map.addRegion(r3);
map.traversal(r1);
map.isConnected();
return 0;
}
这是我的 traversal() 和 isConnected() 方法实现:
void Map::traversal(Region currentNode)
{
visited.push_back(currentNode.getRegionName());
Region* current = ¤tNode;
cout << (*current).getRegionName() << " loc: " << current << endl;
for (auto const & neighbor : (currentNode).getNeighbors())
{
if (std::find(visited.begin(), visited.end(), neighbor.getRegionName()) != visited.end()) {
}
else {
cout << (neighbor).getRegionName() << " neighbors: " << (neighbor).getNeighbors().size() << " location: " << &(neighbor) << endl;
traversal(neighbor);
}
}
}
bool Map::isConnected()
{
cout << visited.size() << endl;
cout << regions.size() << endl;
vector<string> regionList;
for (int i = 0; i < regions.size(); i++)
{
regionList.push_back(regions[i].getRegionName());
}
if (visited.size() == regionList.size())
{
return true;
}
else
{
return false;
}
}
我这里遇到的问题是,由于某种原因,当我在遍历函数递归过程中获取起始节点以外的节点的邻居时,由于某种原因,该函数有时不再记住当前节点的邻居正在遍历的节点((neighbor).getNeighbors().size() 的输出有时会等于 0)。另外,currentNode的地址并不总是与被引用的原始对象的地址相同,导致我认为它是复制对象而不是直接指向其内存位置。
如有任何帮助,我们将不胜感激。我对 C++ 和指针的概念还很陌生。
根据要求,这是我所在地区 class 的代码: Region.h
#pragma once
#include <string>
#include <vector>
using namespace std;
class Region
{
private:
string owner;
string regionName;
int numTokens;
public:
vector<Region> neighbors;
void setOwner(string playerName);
void setRegionName(string name);
void setNumTokens(int num);
void addNeighbor(Region r);
vector<Region> getNeighbors() const;
string getOwner() const;
string getRegionName() const;
int getNumTokens() const;
Region();
Region(string regionName);
~Region();
};
Region.cpp
#include "stdafx.h"
#include "Region.h"
Region::Region()
{
}
Region::Region(string name)
{
regionName = name;
owner = "none";
numTokens = 0;
}
Region::~Region()
{
}
void Region::setOwner(string playerName)
{
playerName = owner;
}
string Region::getRegionName() const
{
return regionName;
}
int Region::getNumTokens() const
{
return numTokens;
}
void Region::setRegionName(string name)
{
regionName = name;
}
void Region::setNumTokens(int num)
{
numTokens = num;
}
void Region::addNeighbor(Region r)
{
neighbors.push_back(r);
}
vector<Region> Region::getNeighbors() const
{
return neighbors;
}
string Region::getOwner() const
{
return owner;
}
在
void Map::traversal(Region currentNode)
currentNode
是按值传递的。这意味着 currentNode
独立于调用 traversal
时作为参数提供的 Region
的副本(任何重要的地方)。这是您用
cout << (*current).getRegionName() << " loc: " << current << endl;
用
修复void Map::traversal(Region & currentNode)
虽然
void Map::traversal(const Region & currentNode)
如果您不打算在函数内部(或作为函数的结果)更改 currentNode
,则首选 。它可以防止错误,并且由于您承诺不更改提供的 Region
,编译器可以利用一些技巧和优化。
下一个陷阱是
vector<Region> neighbors;
存储其中放置的任何内容的副本,而不是原件。所以
r1.addNeighbor(r2);
来电
void Region::addNeighbor(Region r)
也是按值传递(r
是 r2
的副本)和
neighbors.push_back(r);
将 r
的副本放入向量中。最终结果是r1
并不真正知道r2
,它知道一个副本。复制后修改r2
不影响复制。你被困住了。您必须存储指针。
区域需要看起来更像
class Region
{
private:
string owner;
string regionName;
int numTokens;
public:
vector<Region *> neighbors; // change here
void setOwner(string playerName);
void setRegionName(string name);
void setNumTokens(int num);
void addNeighbor(Region * r); // change here
vector<Region *> getNeighbors() const; // change here
string getOwner() const;
string getRegionName() const;
int getNumTokens() const;
Region();
Region(string regionName);
// ~Region(); not required.
};
不相关:Region
不包含非自我管理的资源,因此可以利用 The Rule of Zero。您可以安全地删除它的析构函数。是的。我知道它包含 vector
个原始指针。更多内容请见下文。
这可能会让您陷入内存管理的噩梦,因此您必须确保您拥有那些 Region
每个人都指指点点的东西的所有权。
所有权基本"Who's responsible for the clean-up? Who makes sure what's being pointed at is freed when its no longer required?"
对于您的示例,
Region r1("R1");
是一个自动变量。它管理自己的生命周期。它在超出范围时被释放。你可以
r1.addNeighbor(&r2); //pass raw pointer to r2
和 r2
将在 r1
之前按计划销毁,如果你考虑一下这有点危险。 r1
仍然持有指向 r2
的指针,但 r1
也超出了范围,因此您需要在析构函数中做一些愚蠢的事情或进入多线程。 Region
不需要析构函数,所以你很安全,如果你是多线程的,则需要一组全新的假设,比如为什么你在 main
中超出范围,而你仍然有线程 运行?
但是,如果您要添加和删除 Region
并动态重塑图表,那又如何呢?
这很快就会变得丑陋。
人们通常会选择在 neighbors
中使用智能指针来管理
对他们的记忆。在你的情况下效果不佳。在很多情况下也不适合他们。
vector<unique_ptr<Region>>
(唯一指针)没有任何意义,因为可以有很多 Region
都指向同一个 Region
。如此独特。
shared_ptr
也没有意义,因为 r1
指向 r2
而 r2
指向 r1
。必须采取直接行动来消除循环,这在很大程度上违背了智能指针的要点。如果您忘记或因异常而出轨怎么办?
有可以玩的游戏weak_ptr
,但在双向图中谁是共享的,谁是弱的?
意见警告:我赞成 Region
s 使用原始指针和 Regions
的主列表(再次是 vector<unique_ptr<Region>>
,但这次有效)作为图的成员 class 管理访问、插入、删除和所有其他操作的方式与管理链接节点的链表相同。
可能有我没有看到的更清晰的解决方案。
编辑:这是基于 M.M. 的评论
class Region
{
private:
string owner;
string regionName;
int numTokens;
public:
vector<string> neighbors; // change here. If you have large numbers of neighbours
// consider using a std::set in place of the vector
void setOwner(string playerName);
void setRegionName(string name);
void setNumTokens(int num);
void addNeighbor(const string & name); // change here. Accepting const reference
// reduces unnecessary copying. May want
// to use the same trick for other methods
// receiving a string
const vector<string> & getNeighbors() const; // change here. Returning const
// reference reduces unnecessary copying
string getOwner() const;
string getRegionName() const;
int getNumTokens() const;
Region();
Region(string regionName);
// ~Region(); not required.
};
这假定 regionName
是唯一的,并将其用作从类似于 map<string, Region> masterlist;
的主列表访问 Region
的密钥。 masterlist
管理您所有 Regions
的存储空间。从中删除一个 Region
如果在 masterlist
中找不到 regionName
你不必担心无效指针,你只需记下并从 neighbors
中删除它.
记得小心使用下标运算符。在masterlist[name]
中如果找不到name
,则默认为其构造一个Region
并存储。如果您正在寻找应该存在的 Region
,请首选使用 find
方法。
如果您有固定数量的区域,请考虑使用简单数组或 std::array
代替 masterlist
的 map
并使用 [=24= 的索引] 在数组中作为标识符代替 string
。
补充阅读:What's the difference between passing by reference vs. passing by value?