【问题标题】:Line of Code Doesn't Work Two Lines Later代码行在两行之后不起作用
【发布时间】:2012-12-14 17:24:29
【问题描述】:

这令人费解。我需要在我的程序中使用一个函数 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>;
}

【问题讨论】:

  • 如果您用您所询问的语言标记问题通常会有所帮助,以避免人们猜测。
  • 那么下面两行包含什么?
  • 对不起,新来的。从现在开始我会这样做。
  • 告诉我们CCountry的构造函数,以及局部变量line的类型。
  • 您知道-&gt; 运算符吗?你可以做tempContinent-&gt;addCountry(*tempCountry)

标签: c++ list function


【解决方案1】:

是否有可能在 CCountry 的构造函数中造成了某种覆盖?听起来很适合我。

【讨论】:

  • 我有一个重载的构造函数,它不接受任何参数来创建可变国家,但我认为这不是问题所在。如果你的意思是它再次被调用......你可以看到整个代码。我会在哪里再次调用它?
【解决方案2】:

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

您的 CCountry 课程没有练习 Rule of 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 及其邻居关联。

【讨论】:

    【解决方案3】:

    我怀疑你已经像这样定义了 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 被定义为采用 CCountry,而不是 CCountry &amp;。因此,当您执行(*tempContinent).addCountry(*tempCountry) 时,您的程序会使用编译器定义的CCountry 复制构造函数来制作*tempCountry 的(临时)副本。

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

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

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

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

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

    【讨论】:

    • @SwiftCore 在这种情况下,如果你放弃了指针(你几乎肯定应该这样做),编译器生成的版本会做正确的事情。但无论如何,请获取一份 Scott Meyers 的 Effective C++ 并学习它。它涵盖了许多此类问题。在完成设计之前不要担心编码:在决定如何处理复制和分配之前,您必须知道类在应用程序中的作用。例如,它是否具有身份(在这种情况下,您不应该复制它)。
    【解决方案4】:

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

    【讨论】:

    • 我怀疑你说的是真的,但你怎么知道?他小心翼翼地避免向我们展示他的代码中任何有趣的部分,比如复制构造函数或析构函数。
    【解决方案5】:

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

    你显示的不是CCountry的定义,而是你的方式 初始化name 建议您假设 std::string 是一个实体对象。不是——它有价值 语义,并且几乎没有任何情况下你会有 指向std;:string 的指针。 (一个例外是作为 您想要支持的函数参数或返回值 一个空指针,表示没有值。)相同 playerunitsneighbors 的情况:上下文 您将有一个指向 int 或标准的指针 容器仅限于需要空指针的情况 表示没有值。

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

    最后,关于一个完全不相关的问题:预先安排你的课程 带有C 的名称​​不是一个好主意。微软采用了这个 他们的类名的约定(至少在他们的一些 图书馆),任何看到像 CCountry 这样的名字的读者都会 假设它是来自 Microsoft 库之一的类, 并将尝试在 Microsoft 文档中找到它,而不是 在你的代码中。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-03-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-01-09
      • 2018-08-16
      • 2017-10-13
      • 2015-08-28
      相关资源
      最近更新 更多