循环遍历 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 = &currentNode;
    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)

也是按值传递(rr2 的副本)和

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 指向 r2r2 指向 r1。必须采取直接行动来消除循环,这在很大程度上违背了智能指针的要点。如果您忘记或因异常而出轨怎么办?

有可以玩的游戏weak_ptr,但在双向图中谁是共享的,谁是弱的?

意见警告:我赞成 Regions 使用原始指针和 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 代替 masterlistmap 并使用 [=24= 的索引] 在数组中作为标识符代替 string

补充阅读:What's the difference between passing by reference vs. passing by value?