3

这令人费解。我需要在我的程序中使用一个函数 CCountry::getName()。奇怪的是,在测试它是否完全有效时,它在一个地方有效,但在两行以下不起作用,我不知道为什么。例如...

while(line != "---" && line != "------")
    {
        CCountry *tempCountry = new CCountry(line);
        cout << tempCountry->getName() << flush;
        (*tempContinent).addCountry(*tempCountry);
        getline(filestr, line);

    }

作品。它按顺序列出了所有国家/地区名称。然而...

    while(line != "---" && line != "------")
    {
        CCountry *tempCountry = new CCountry(line);
        (*tempContinent).addCountry(*tempCountry);
        getline(filestr, line);
        cout << tempCountry->getName() << flush;
    }

不工作。它甚至无法打印一个国家/地区名称,而是在调用 getName() 的行上抛出一个段错误。

这里有两个函数供进一步参考,getName() 和 addCountry()

string CCountry::getName()
{
return *name;
}

void CContinent::addCountry(CCountry country)
{
(*countries).push_back(country);
}

根据请求,这里是 CCountry 构造函数:

CCountry::CCountry(string in_name)
{
name = new string;
*name = in_name;
player = new int;
*player = -1;
units = new int;
*units = 0;
neighbors = new list<CCountry>;
}
4

5 回答 5

3

我可以喋喋不休地列出一长串代码有问题的地方,但导致你的错误的最终原因如下:

您的 CCountry 课程没有练习3 规则,因为它具有动态分配的成员,所以必须这样做。(顺便说一句,甚至不需要)。

您正在通过按值获取国家/地区的成员函数将您的 CCounty 对象添加到您的大陆。当时制作了对象的浅表副本。然后,您将其推送到大陆内的容器中,这会生成另一个浅表副本。在 addCountry() 退出时,原始浅表副本被破坏,并且在此过程中,当您返回调用代码时,CCountry 对象的内部已被破坏。因此,您的本地(不应该首先动态分配,顺便说一句)被正式冲洗。

你猜怎么着……你大陆集装箱里的那个也是。

我可能会首先考虑 CCountry 对象本身。就个人而言,我会在 CContinent 类而不是 CCountry 中管理 CCountry 的邻居,因为无论如何管理 CCountry 对象的集合,但对每个人都是自己的。如果您决定坚持使用当前模型,CCountry 的潜在替代方案可能是这样的:

class CCountry
{
public:
    CCountry(const std::string& name)
       : name(name), player(0), units(0)
    {
    }

    // properties
    const std::string& getName() const { return name; };
    int getPlayer() const { return player; };
    void setPlayer(int player) { this->player = player; };
    int getUnits() const { return units; };
    void setUnits(int units) { this->units = units; };

    // neighbor access
    const std::list<const CCountry*> getNeighbors() const
    {
        std::list<const CCountry*> res;
        for (auto it=neighbors.begin(); it != neighbors.end(); ++it)
            res.push_back(it->second);
        return res;
    }

    // adding a new neighbor
    void addNeighbor(const CCountry& other)
    {
        neighbors[ other.getName() ] = &other;
    }

private:
    std::string name;
    int player;
    int units;
    std::map<std::string, const CCountry*> neighbors;
};

但请注意:追求这样的模型(正如您所看到的,您的原始模型)将有潜在的陷阱,特别是 CCountry 可能具有指向另一个 CCountry 的指针的可能性,该指针在技术上 它并不拥有。这就是为什么我更喜欢由 CContinent 类本身管理邻居关联的原因,因为它同时拥有 CCountry 及其邻居关联。

于 2012-12-29T23:00:40.980 回答
2

我怀疑您已经定义了CCountry这样的析构函数:

~CCountry() {
    delete name;
    delete player;
    delete units;
    delete neighbors;
}

但我怀疑你没有CCountry. 这意味着编译器正在生成这样的复制构造函数:

CCountry(CCountry const &that) :
    name(that.name),
    player(that.player),
    units(that.units),
    neighbors(that.neightbors)
{ }

现在,CContinent::addCountry被定义为采用 a CCountry,而不是 a CCountry &。因此,当您这样做时,您的程序会使用该编译器定义的复制构造函数来(*tempContinent).addCountry(*tempCountry)制作(临时)副本。*tempCountryCCountry

所以现在你的程序有两个独立的实例CCountry:一个由 指向tempCountry,另一个在CContinent::addCountry'country参数中。但是由于编译器定义的复制构造函数的工作方式,两个实例都有name指向同一个string实例的成员变量。

删除临时副本时,其析构函数会删除该字符串实例。现在 指向的实例在其成员变量tempCountry中有一个悬空指针。name当您尝试取消引用该悬空指针时getName,该行为未定义,并导致您的分段错误。

将您的nameplayerunitsneighbors成员变量更改为不是指针。它们应该只是普通类型,如下所示:

class CCountry {
    string name;
    int player;
    int units;
    list<CCountry *> neighbors;
};

您可能还想更改函数以获取引用而不是副本。

于 2012-12-29T23:01:54.377 回答
0

您是否有可能在 CCountry 的构造函数中造成某种覆盖?听起来对我来说。

于 2012-12-29T22:51:52.217 回答
0

在 CCountry 的构造函数中,您使用 分配名称,new string而在析构函数中,您可能使用delete name. 我不知道你为什么需要这样做,它可能更简单存储而string不是string*作为name. CCountry当您将CCountry其作为参数传递给CContinent::addCountry临时副本时,它将被创建然后删除,这将导致删除CCountry::name在多个CCountry. 为避免这种情况,您需要使用 string而不是作为 CCountry的string*成员或实现您自己的 CCountry 复制构造函数。nameCCountry

于 2012-12-29T23:05:28.457 回答
0

这段代码有很多问题,但对于初学者来说,确实CCountry有值语义或者它是一个实体类型。在第一种情况下,您不应该有指向它的指针,或者使用动态分配它new。并且您应该确保它可以被正确复制和分配。其次,您不应该将其按值传递给CContinent::addCountry(并且您可能应该通过将复制构造函数和赋值运算符设为私有或从 派生来禁止复制和赋值boost::noncopyable)。

您没有显示 的​​定义CCountry,但您初始化的方式name表明您假设它 std::string是一个实体对象。它不是——它具有值语义,并且几乎没有任何情况下你会有一个指向 a 的指针std;:string。(一个例外是作为函数参数或返回值,您希望支持空指针来指示值的缺失。)同样的事情适用于player,unitsneighbors: 上下文,您将有一个指向int或指向标准容器仅限于需要空指针来指示缺少值的情况。

您也没有展示使用复制构造函数、赋值运算符或析构函数。如果您在析构函数中删除内存,并且没有复制构造函数,那么这就是问题的根源。编译器生成的复制构造函数执行浅复制,这意味着当您调用 时 CContinent::addCountry,您最终会得到两个具有相同指针的对象。当参数被破坏时,如果它删除任何东西,这会使作为参数传递的对象无效(它包含相同的指针)。有不同的方法来处理这个问题,但在几乎所有情况下,最合适的是不使用指针。(std::string例如,类有一个复制构造函数,它可以进行深度复制,所以使用它没有问题。)

最后,关于一个完全不相关的问题:在你的类名前面加上C不是一个好主意。微软已经为其类名采用了这种约定(至少在他们的一些库中),任何看到类似名称的读者都会认为它是来自微软库之一的类,并会尝试在微软文档中找到它,而不是在您的代码中。CCountry

于 2012-12-29T23:09:02.760 回答