返回对象丢失属性

时间:2017-08-02 20:19:38

标签: c++ qt deserialization

这基本上是我的主要功能:

void    GameObject::deserialize(QList<GameObject*> *list)
{
  QFile _file(_filename);
  if (!_file.open(QIODevice::ReadOnly | QFile::Text))
  {
    qDebug() << "Couldn't open file to read";
    return;
  }
  QTextStream in(&_file);

  while (!in.atEnd())
  {
    QString currentline = in.readLine();
    if (currentline.startsWith("GameObject {"))
    {
      list->append(getGameObject(&in, currentline));
    }
  }
  _file.close();
}

它打开一个文件,并为GameObject s解析它。

getGameObject (已清理)功能就是这个:

GameObject    *GameObject::getGameObject(QTextStream *in, QString current_line)
{
  GameObject *go = new GameObject();
  QList<GameObjectVariable*> govl;

  bool ok;
  while (!in->atEnd() && !current_line.startsWith("} end GameObject"))
  {
    current_line = in->readLine();
    if (current_line.startsWith("GameObjectVariable")) {
      GameObjectVariable *gov = this->getGameObjectVariable(in, current_line);
      govl.append(gov);
      go->setVariableList(&govl);
      qDebug() << QString("AR : Type As String of the returned object : ") + gov->getTypeAsAString();
      qDebug() << QString("AR : Type as number : ") + QString::number(gov->getType());
      if (gov->getType() == NUMBER_LIST) {
        qDebug() << "Number List";
        qDebug() << QString("Value as a string of the number list : ") + ((GameObjectVariableNumberList*)gov)->getValueAsAString();
      }
      else if (gov->getType() == STRING_LIST) {
        qDebug() << "String List";
        qDebug() << QString("Value as a string of the string list : ") + ((GameObjectVariableStringList*)gov)->getValueAsAString();
      }
      else if (gov->getType() == GAMEOBJECT) {
        qDebug() << "GameOBject";
        qDebug() << QString("Value as a string of the gameobject : ") + ((GameObjectVariableGameObject*)gov)->getValueAsAString();
      }
    }
  }
  return go;
}

这个基本上是if s的大混乱,但它起作用,直到某一点。此功能的目标是逐行读取,并返回填充了读取信息的游戏对象。这是我们用于项目的一种奇怪的格式。

最后一行是调试行,我试图了解问题所在。

这是与之相关的其他(已清理)功能:

GameObjectVariable* GameObject::getGameObjectVariable(QTextStream *in, QString current_line)
{
    GameObjectVariable *gov;
    bool ok;
    int type;
    QList<QString> ls;
    QList<int> ln;
    QList<GameObject*> lgo;


    while (!in->atEnd() && !current_line.startsWith("} end GameObjectVariable")) {
        current_line = in->readLine();
        if (current_line.startsWith("type: "))
        {
            type = current_line.right(current_line.size() - 5).toInt(&ok, 10);
        }
        else if (current_line.startsWith("value: ")) {
            if (current_line.startsWith("value: {"))
            {
                while (!in->atEnd() && !current_line.startsWith("} end value"))
                {
                    current_line = in->readLine();
                    if (!current_line.startsWith("} end value"))
                    {
                        if (type == GAMEOBJECT_LIST)
                        {
                            lgo.append(getGameObject(in, current_line));
                        }
                        else if (type == STRING_LIST)
                        {
                            ls.append(current_line);
                        }
                        else if (type == NUMBER_LIST)
                        {
                            ln.append(current_line.toInt(&ok, 10));
                        }
                    }
                }
                if (type == GAMEOBJECT_LIST)
                   ((GameObjectVariableGameObjectList*) gov)->setValue(&lgo);
                else if (type == STRING_LIST)
                    ((GameObjectVariableStringList*) gov)->setValue(&ls);
                else if (type == NUMBER_LIST)
                    ((GameObjectVariableNumberList*) gov)->setValue(&ln);
            }
        }
    }
    qDebug() << QString("BR : Get the type as string : ") + gov->getTypeAsAString();
    qDebug() << QString("BR : Get the type as number : ") + QString::number(gov->getType());
    qDebug() << QString("BR : get the value as string of the object : ") + gov->getValueAsAString();

    return gov;
}

此功能读取GameObjectVariable&#39;标记&#39;中的行。该类型是一个已定义的int,它与我们用于其他if林的文本类型相关联。 现在再次正常工作,除非我们有一个值列表(以else if (current_line.startsWith("value: {"))开头的部分)。

函数末尾的调试行(&#34; BR:&#34; 1)显示正确填充的对象,但是getGameObject调用函数末尾的调试行(以&#34; AR:&#34;开头)崩溃,因为显然该值为空。

GameObjectVariable对象是这个(再次,清理)

class GameObjectVariable
{
public:
    GameObjectVariable(QString name, QList<int> idListEdit = QList<int>(), QList<int> idListView = QList<int>());

    // GETTERS
    QString                         getName()                                       {return this->name;}
    int                             getType()                                       {return this->type;}
    void                            *getValue()                                     {return this->value;}

    // SETTERS
    void                            setName(QString name)                           {this->name = name;}
    void                            setValue(void* value)                           {this->value = value;}

    QString                         getTypeAsAString();
    virtual QString                 getValueAsAString() = 0;

private:
    QString                         name;

protected:
    void                            *value;
    int                             type;
};

getValueAsAString设置为虚拟,因为上面代码中提到的每种类型(例如GameObjectVariableStringList都会使用正确的类型返回value来覆盖此类型)

最后,这是我们尝试反序列化的文件示例:

GameObject {
name: Number 1
type: Test
GameObjectVariableStringList: {
type: 3
name: List String
value: {
String 1
String 2
} end value
} end GameObjectVariable

type: 3对应STRING_LIST

主要问题是粗体。

1 个答案:

答案 0 :(得分:4)

问题

getGameObjectVariable()的本地变量GameObjectVariable *gov是一个未初始化的指针,您突然转换为其他类型,然后开始尝试调用方法。

你是怎么期望结束的?你告诉编译器:在随机存储器中戳,好像它拥有一个已分配的初始化对象。此外,此对象可能有3种不同的类型。

说真的:你认为在那个函数中发生了什么,它是以某种方式生成一个可用的对象?我真的很好奇。

无论如何,至少有三个原因,这是错误的代码,表现出完全未定义的行为:

  • 指针未初始化,因此与任何未初始化的变量一样,读取/取消引用它是UB。
  • 在它所指向的(无效)地址上没有对象存在,但是你在它上面调用方法就好像一个对象住在那里;那是UB。
  • 然后,所述方法可能会开始将内容写入您最终遇到的任意地址,没有许可,即致命 UB。

(另外,即使有有效的内存要访问并且有一个有效的对象,将指针转换为另一种类型然后使用它只有在该地址专门分配了另一种类型的实例时才有效 - 或者更多 - 派生的,但是C风格的演员是(A)糟糕的风格和(B)可能非常危险,例如,如果正在进行多重和/或虚拟继承。)

由于所有这些UB,任何事情都可能发生,或者什么都不会发生,或者正是你想要的可能发生 - 但是代码从根本上被打破了。

例如,正如这里似乎已经发生的那样,编译器可能巧合地表示在同一个函数中存在有效对象,但是然后将该垃圾指针返回到getGameObject() ,它突然显示你喂它垃圾。

UB给编译器,特别是它的优化层,自由地执行他们想做的任何事情,主要是因为他们被允许假设UB不会发生。所以,例如他们可以假设必须有gov指向的有效对象,即使公然没有。但是,无论出于什么原因,你回来后都会失去这种假设。

谁知道?观察到的行为的确切原因是非常无趣的推测。如果你真的想知道发生了什么,你可以产生汇编输出。

(立即)解决方案

但关键在于:你需要用适当的,有效的代码替换这个特殊的混乱 - 以及快速。因此,您需要为指针指定一个有效值,方法是为其指定一个新分配的对象的地址,该对象需要任何类型。只有这样,您才能拥有一个允许访问的地址,其中包含一个正确类型的活动对象。然后可以创建实际派生类型的强制转换指针来调用派生方法,但是返回指向其他用户的指针。

有条件地调用方法等

此外,那些条件转换和对setValue()的调用看起来很可疑。为什么不让它成为一个虚方法并让编译器解决正确的变化?通常,如果你有一些条件构造决定基于真实类型调用哪个方法......你应该只使用虚函数。最关心的是他们的开销是FUD,大多数尝试避免这种开销都不会更有效地执行,而且阅读起来更糟糕。

例如,您是否希望任何GameObjectVariable的所有用户重复相同的跳跃练习,检查它是什么类型并转换为等效类型的指针来调用右边的(派生,隐藏 - 不是-overriding)版本setValue()?你好,样板意粉代码,无缘无故。

我认为这指出了你设计中更普遍的坏模式。而不是具有重复必须检查类型和执行不同事物的大型函数,具有取决于类型的不同列表等 - 为什么不简单地检查输入行指定的类型,并构造具有该类型的新对象,例如,该行的其余部分作为参数,让它创建和填充任何类型的列表和它需要的任何其他特定属性?那么你将拥有做单件事的整洁方法,而不是必须不断提醒自己与他们合作的对象的迷宫。

避免new

请注意,我说要分配来自&#34;新分配的对象的指针&#34;,而不是一个new分配的对象 ...大多数人不应该永远需要在C ++中使用原始newdelete,因此您应该返回unique_ptr,最好是std::make_unique<Foo>(args)

如果hyde在注释中指出,您的新对象属于一个应该由其添加的父对象管理其生命周期的类型,则存在公平的异常。然后new就行了 - 假设没有更好的方式来表达它,就像我不知道,make_floating_reference<Foo>(args)。但是,正如hyde所说的那样,GameObjectVariable的情况并非如此,所以智能指针是实现这一目标的方法。

(通常我会说你可能根本不需要动态分配,但是因为你似乎需要多态性而且对象显然不包含堆栈上的已知集合,你可以推送非将指针/引用包装器放入容器中,似乎就是这样做。)