当我不期望C ++ Destructor被调用时

时间:2019-06-24 23:13:22

标签: c++ destructor

我在一些代码中添加了一个析构函数,它似乎在尽早调用并引起问题。我添加了一条调试语句以查看它的调用位置,这使我更加困惑。我了解管理自己的记忆不是最佳做法,但我想亲自尝试一下。

My debug output

这基本上是我的GameObject类:

class min
{
    public static void Main(string[] args)
    {
        min p = new min();
    }

    public min()
    {
        DetailsMenu(1);
    }

    List<string> active = new List<string>() { "Example1", "Example2", "Example3" };

    string input = "";
    int index;

    /// <summary>
    /// Creates a detail menu
    /// </summary>
    /// <param name="index">What name</param>
    public void DetailsMenu(int index)
    {
        Console.CursorVisible = false;
        this.index = index;
        Thread Input = new Thread(new ThreadStart(DetailMenuInput));
        Thread Details = new Thread(new ThreadStart(DetailsMenuThread));
        Input.Start();
        Details.Start();
    }

    /// <summary>
    /// Displays the screen it self
    /// </summary>
    void DetailsMenuThread()
    {
        while (true)
        {
            Console.Clear();
            Console.WriteLine($"{active[index - 1]}");
            Console.SetCursorPosition(0, Console.WindowHeight - 1);
            Console.WriteLine($">{input}_");
            Console.SetWindowPosition(0, 0);
            Thread.Sleep(100);
        }
    }

    /// <summary>
    /// Needs to handle input
    /// in this function i need to press a key twice to add it to input
    /// </summary>
    void DetailMenuInput()
    {
        while (true)
        {
            Console.TreatControlCAsInput = false;
            var key = Console.ReadKey(true);
            if (key.Key == ConsoleKey.Backspace)
            {
                if (input.Length > 0)
                    input = input.Remove(input.Length - 1);
                continue;
            }
            input += Console.ReadKey(true).KeyChar.ToString();
        }
    }
}

这是createPlayer方法:

class GameObject
{
public:
int             xCoord = 0, yCoord = 0, prevX, prevY;
int             status = 0, weight = -1;
int             id = -1;

GameObject(CommandComponent* commands,
    PhysicsComponent* physics,
    GraphicsComponent* graphics)
    : commandComponent(commands),
    physicsComponent(physics),
    graphicsComponent(graphics)
{};

~GameObject()
{
    std::cout << "Destructor called " << id << std::endl;
    delete commandComponent;
    delete physicsComponent;
    delete graphicsComponent;
};

void update(World& world, int command, sf::Time dt)
{
    commandComponent->update(*this, world, command);
    physicsComponent->update(*this, world);
    graphicsComponent->update(*this, dt);
};

void update(World& world, int command)
{
    commandComponent->update(*this, world, command);
    physicsComponent->update(*this, world);
};

sf::Sprite draw()
{
    return *(graphicsComponent->draw());
};

void setCoords(int x, int y)
{
    prevX = xCoord;
    xCoord = x;
    prevY = yCoord;
    yCoord = y;
};

void setId(int newId)
{
    id = newId;
}


private:
    CommandComponent*           commandComponent    = NULL;
    GraphicsComponent*          graphicsComponent   = NULL;
    PhysicsComponent*           physicsComponent    = NULL;

};

我调用此方法将新对象添加到向量,基于它是活动对象还是非活动对象,我还将其添加到数组:

    GameObject* createPlayer(sf::Texture* text)
{
    return new GameObject(new PlayerCommandComponent(), new PlayerPhysicsComponent(), new PlayerGraphicsComponent(text));
};

最后,这是我的测试代码,该代码创建游戏对象并调用上面的函数,在此我看到从以下位置调用析构函数:

void World::addObject(GameObject object, int id, int type){
object.setId(id);

if (type == 0)
{
    inactiveObjects.push_back(object);
}
else if (type == 1)
{
    activeObjects.push_back(object);
}
}

我想我的主要问题是如何调用Destructors?我假设它与我在createPlayer方法中构造对象的方式有关,也许在返回它后它就超出范围了,但是我认为使用new关键字可以防止这种情况的发生?我在这里感到困惑。

1 个答案:

答案 0 :(得分:2)

这里有几件事在起作用。

GameObject* createPlayer(sf::Texture* text)

返回动态分配的GameObjectThis could be done better,继续阅读std::unique_ptr,但这里没有严格的错误。我主要是为了指出std::unique_ptr并进行设置

addObject((*createPlayer(&(mTextures.get(Textures::PlayerCharacter)))), 0, 1);

因为这是开始出错的地方。当您找到使用new并取消引用并丢弃结果的代码时,您正在查看的是内存泄漏。您已经失去了指向动态分配对象的指针,并且没有了指针,几乎不可能再次找到分配,因此可以delete

存储解引用的对象将调用复制构造函数或赋值运算符,此时,您需要考虑The Rule of Three:如果需要使用a来定义自定义析构函数,则可能需要定义一个自定义赋值运算符和副本构造函数。这是何时需要观察Rule of Three的标准示例。在Rule of Three链接中可以清楚地解释问题所在,因此请先停止阅读并了解它,然后再继续进行操作。否则,此答案的其余部分将对您几乎无用。

如果不牢牢把握the Rule of Three and all of its friends,就不能编写出色的C ++代码。

您可以通过更改

来绕过三规则
void World::addObject(GameObject object, int id, int type)

void World::addObject(GameObject * object, int id, int type)

,并通过引用传递object。这没什么帮助,因为

inactiveObjects.push_back(object);

期望一个对象,而不是指针。

您也可以更改它,但是应该吗? std::vector在直接包含对象时处于绝对最佳状态。指针导致指针追逐,不良的缓存行为,最终导致suuuhhhfering。除非您有充分的理由,否则不要存储指针。

如果这样做,则以std::unique_ptr开始到结束来管理指针。

我该怎么做:

直接跳过三个规则,然后转到The Rule of Five

  1. 消灭尽可能多的动态分配的变量,这样我就不需要对点2做太多的工作。这意味着没有指针指向commandComponentphysicsComponent(或其中)和graphicsComponent
  2. GameObject以及CommandComponentPhysicsComponentGraphicsComponent添加移动构造函数并将赋值运算符移动到GraphicsComponent。使所有资源管理尽可能靠近资源。这使您可以使更高级别的类尽可能地无知。如果GameObject知道如何复制和移动自己,则GameObject不需要知道如何移动它。这使您可以利用The Rule of Zero的优势,零规则应该是您在所有课程中都追求的目标。
  3. 使用移动语义来获取GameObject*createPlayeractiveObjects inactiveObjects的{​​{1}},而不是vector。 / li>
  4. 享受减少的内存管理负担。