在内存中修改自身的C ++对象

时间:2014-10-24 14:41:05

标签: c++

我的一位朋友和其他人写了下面的代码,根据我的C ++知识应该是非常危险的:

Recovery& Recovery::LoadRecoverFile() {
    fstream File("files/recover.dat", ios::in | ios::binary);
    if (File.is_open()) {

        while (!File.eof()) {
            File.read(reinterpret_cast<char*>(this), sizeof(Recovery));  // <----- ?
        }
    }
    File.close();
    return *this; // <----- ?
}

你能否告诉我为什么这很糟糕,应该如何正确地完成?

他们基本上将类Recovery的对象写入文件,并在需要时使用上述方法读取它。

编辑: 只是提供一些有关代码的其他信息。这就是恢复类所包含的内容。

class Recovery {
public:
    Recovery();
    virtual ~Recovery();
    void createRecoverFile();
    void saveRecoverFile( int level, int win, int credit, gameStates state, int clicks );
    Recovery& LoadRecoverFile();
    const vector<Card>& getRecoverCards() const;
    void setRecoverCards(const vector<Card>& recoverCards);
    int getRecoverClicks() const;
    void setRecoverClicks(int recoverClicks);
    int getRecoverCredit() const;
    void setRecoverCredit(int recoverCredit);
    int getRecoverLevel() const;
    void setRecoverLevel(int recoverLevel);
    gameStates getRecoverState() const;
    void setRecoverState(gameStates recoverState);
    int getRecoverTime() const;
    void setRecoverTime(int recoverTime);
    int getRecoverWin() const;
    void setRecoverWin(int recoverWin);

private:
    int m_RecoverLevel;
    int m_RecoverCredit;
    gameStates m_RecoverState;
};

这会将对象保存到文件中:

void Recovery::saveRecoverFile(int level, int win, int credit, gameStates state,
        int clicks) {

    m_RecoverLevel = level;
    m_RecoverCredit = credit;
    m_RecoverState = state;

    ofstream newFile("files/recover.dat", ios::binary | ios::out);
    if (newFile.is_open()) {
        newFile.write(reinterpret_cast<char*>(this), sizeof(Recovery));
    }

    newFile.close();
}

它是如何使用的:

    m_Recovery.LoadRecoverFile();
    credit.IntToTextMessage(m_Recovery.getRecoverCredit());
    level.IntToTextMessage(m_Recovery.getRecoverLevel());
    m_cardLogic.setTempLevel(m_Recovery.getRecoverLevel());
    Timer::g_Timer->StartTimer(m_Recovery.getRecoverLevel() + 3);

4 个答案:

答案 0 :(得分:2)

可能是undefined behavior(除非Recovery是仅由标量字段构成的POD)。

如果Recovery类有一个vtable,它可能无法工作,除非正在读取的进程可能与编写它的进程相同。 Vtables包含函数指针(通常是某些机器代码的地址)。并且这些函数指针将从一个进程变化到另一个进程(即使它们运行相同的二进制文件),例如,因为ASLR

如果Recovery包含其他对象(例如std::vector<std::shared_ptr<Recovery>> ....或您的gameStates),它也无法工作,因为这些子对象赢了&#39 ;正确构造。

它有时可以工作。但您显然正在寻找的是serialization(然后我会建议使用JSON等文字格式,但另请参阅libs11n)或application checkpointing。您应该从一开始就为这些目标设计应用程序。

答案 1 :(得分:2)

这实际上取决于Recovery对象包含的内容。如果它包含指向数据的指针,那么打开资源描述符以及类似的东西,你将无法以有意义的方式将它们存储在文件中。以这种方式恢复指针可能会设置其值,但它指向的值肯定不会是您预期的值。

如果Recovery是POD,这应该有效。

您可能需要查看与您类似的this questionthis other question


正如Galik正确指出的那样,使用

while (!File.eof()) {

没有多大意义。相反,你应该使用

if ( File.read(/* etc etc */) ) {
    // Object restored successfully.
}
else {
   // Revert changes and signal that object was not loaded.
}

函数的调用者需要知道加载是否成功。该方法已经是一个成员函数,因此更好的定义可能是:

/* Returns true if the file was read successfully, false otherwise.
 * If reading fails the previous state of the object is not modified.
 */
bool Recovery::LoadRecoverFile(const std::string & filename);

答案 2 :(得分:1)

我个人建议以文本格式而不是二进制格式存储游戏状态。像这样的二进制数据是不可移植的,有时甚至在同一台计算机上的同一编译器的不同版本之间,甚至使用不同的编译器配置选项。

如果您要使用二进制路由(或不是),我会看到代码中出现的主要问题是缺少错误检查。让Recovery对象通过自己的petard提升自身的整个想法使错误检查变得非常困难。

我已经敲了一些我觉得更健壮的东西。我不知道您正在使用的正确程序结构,因此这可能无法满足您的需求。但它可以作为如何处理这个问题的一个例子。

最重要的是始终 检查错误在适当的时候报告将它们返回给来电者。< / p>

enum gameStates
{
    MENU, STARTGAME, GAMEOVER, RECOVERY, RULES_OF_GAMES, VIEW_CARDS, STATISTIC
};

const std::string RECOVER_FILE = "files/recover.dat";

struct Recovery
{
    int m_RecoverLevel;
    int m_RecoverCredit;
    gameStates m_RecoverState;
};

struct WhateverClass
{
    Recovery m_Recovery;
    bool LoadRecoverFile(Recovery& rec);

public:
    bool recover();
};

// Supply the Recover object to be restored and
// return true or false to know it succeeded or not
bool WhateverClass::LoadRecoverFile(Recovery& rec)
{
    std::ifstream file(RECOVER_FILE, std::ios::binary);

    if(!file.is_open())
    {
        log("ERROR: opening the recovery file: " << RECOVER_FILE);
        return false;
    }

    if(!file.read(reinterpret_cast<char*>(&rec), sizeof(Recovery)))
    {
        log("ERROR: reading from recovery file: " << RECOVER_FILE);
        return false;
    }

    return true;
}

bool WhateverClass::recover()
{
    if(!LoadRecoverFile(m_Recovery))
        return false;

    credit.IntToTextMessage(m_Recovery.getRecoverCredit());
    level.IntToTextMessage(m_Recovery.getRecoverLevel());
    m_cardLogic.setTempLevel(m_Recovery.getRecoverLevel());
    Timer::g_Timer->StartTimer(m_Recovery.getRecoverLevel() + 3);

    return true;
}

希望这有帮助。

答案 3 :(得分:-1)

大家好吧其实类StateManager内容整数:

#ifndef STATEMANAGER_H_
#define STATEMANAGER_H_
enum gameStates {

    MENU, STARTGAME, GAMEOVER, RECOVERY, RULES_OF_GAMES, VIEW_CARDS, STATISTIC

};

class StateManager {
public:
    static StateManager* stateMachine;
    StateManager();
    virtual ~StateManager();
    gameStates getCurrentGameStates() const;
    void setCurrentGameStates(gameStates currentGameStates);
private:
    gameStates m_currentGameStates;
};

#endif /* STATEMANAGER_H_ */
相关问题