Ukryte działanie std::vector błąd z pamiecią

0

Witam, mam problem z biblioteką box2D oraz std::vector. Wydaje mi się że problem raczej nie dotyczy samej biblioteki.
W kodach poniżej próbuje stworzyć "ciało fizyczne" a następnie je usunąć:
Działający kod:


#include <Box2D/Box2D.h>
int main()
{

   b2Vec2 gravity(0.0f,10.0f);
   b2World world(gravity, true);

    b2BodyDef bodyDef;
    bodyDef.type = b2_dynamicBody;

    bodyDef.position.Set(1.0f, 1.0f);
    b2Body* physical = world.CreateBody(&bodyDef);

    world.DestroyBody(physical);

    }
}

Problem pojawia się, gdy próbuje stworzyć klasę która bedzie odpowiedzialna za ciało:
Nadal działający kod:

class shape
{
public:
    shape(b2World&);
    ~shape();

private:
  b2Body* physical;
};


shape::~shape()
{
    if(physical != nullptr) {
    physical->GetWorld()->DestroyBody(physical);
    physical = nullptr;
    }
}
shape::shape(b2World& world)
: physical(nullptr)
{
    b2BodyDef bodyDef;
    bodyDef.type = b2_dynamicBody;


    bodyDef.position.Set(1.0f, 1.0f);
    physical = world.CreateBody(&bodyDef);
    //skipping fixture etc..

}


int main()
{

b2Vec2 gravity(0.0f,10.0f);
b2World world(gravity, true);

shape test(world);

}

Ostateczny problem pojawia się, gdy próbuje trzymać klasę w std:
Crash:

class shape
{
public:
    shape(b2World&);
    ~shape();

private:
  b2Body* physical;
};


shape::~shape()
{
    if(physical != nullptr) {
    physical->GetWorld()->DestroyBody(physical);
    physical = nullptr;
    }
}
shape::shape(b2World& world)
: physical(nullptr)
{
    b2BodyDef bodyDef;
    bodyDef.type = b2_dynamicBody;


    bodyDef.position.Set(1.0f, 1.0f);
    physical = world.CreateBody(&bodyDef);
    

}

int main()
{
b2Vec2 gravity(0.0f,10.0f);
b2World world(gravity, true);

std::vector<shape> objects;
objects.push_back(shape(world));

// Nie musze nawet zacząć usuwać objektów zeby program się rozwalił
// co jest w sumie naturalne, gdyż koniec main() usuwa wszystko z vectora.


Program kompiluje sie, a następnie po uruchomieniu po prostu następuje crash. W konsoli mogę przeczytać: Assertion failed

2

A to nie jest po prostu tak, że jak tutaj:

    b2BodyDef bodyDef;
    bodyDef.type = b2_dynamicBody;

sobie tworzysz to bodyDef, a potem tutaj:

    physical = world.CreateBody(&bodyDef);

przekazujesz jego adres, to po wyjsciu z konstruktora bodyDef nie istnieje?

A przykład działający po prostu nie usuwa bodyDef (nie wychodzi za jego scope) przed końcem programu, więc nie widać problemu.

0
n0name_l napisał(a):

A to nie jest po prostu tak, że jak tutaj:

    b2BodyDef bodyDef;
    bodyDef.type = b2_dynamicBody;

sobie tworzysz to bodyDef, a potem tutaj:

    physical = world.CreateBody(&bodyDef);

przekazujesz jego adres, to po wyjsciu z konstruktora bodyDef nie istnieje?

A przykład działający po prostu nie usuwa bodyDef (nie wychodzi za jego scope) przed końcem programu, więc nie widać problemu.

Z dokumentacji:
b2Body* b2World::CreateBody ( const b2BodyDef * def )
Create a rigid body given a definition. No reference to the definition is retained.
Warning:
This function is locked during callbacks.

Z tego co zrozumialem, funkcja kopiuje sobie objekt?

5

Z tego co zrozumialem, funkcja kopiuje sobie objekt?

Tak, tak, to był dziki strzał. ;-)

2 strzał, teraz czuje, że to to: Wrzucając do wektora robisz następujące czynności:

  1. Tworzysz obiekt.
  2. Kopiujesz obiekt używając konstruktora kopiującego (domyślny zostaje użyty - kopiuje tylko wskaźnik!).
  3. Na pierwszym obiekcie wywołuje się destruktor (niszczy twoje ciało ze świata, wskaźnik z punktu wyżej już nie może być używany)
  4. Dopiero teraz w wektorze masz sprawny obiekt.

Look.

#include <iostream>
#include <vector>
using namespace std;

class Foo {
public:
	Foo() { cout << "ctor()\n"; }
	~Foo() { cout << "dtor()\n"; }
	Foo(const Foo&) { cout << "cctor()\n"; }
};

int main() {
	vector<Foo> v;
	v.push_back(Foo());
	cout << "xxx\n";
	return 0;
}

http://ideone.com/apbGYq

Rozwiązanie: Zdefiniuj konstruktor kopiujący. Chociaż w tym przypadku chyba lepiej będzie po prostu trzymać wektor wskaźników (najlepiej jakichś inteligentnych).

Ad tego dlaczego się crashuje jak nic z tym nie robisz.
Po usunięciu pierwszego (tymczasowego) obiektu, przy wrzucaniu do wektora, usuwasz ciało ze świata i ustawiasz lokalny wskaźnik na nullptr, ale (!) w skopiowanym obiekcie, ten wskaźnik jest ustawiony na adres do usuniętego ciała (nie jest równy nullptr), wywołuje się drugi destruktor i dostajesz crasha.

0

@n0name_l
świetna analiza i rozwiązanie :) Problem rozwiązany! Męczyłem się z tym od paru dni, dałbym Ci tysiąc plusów gdyby się dało :)
Co prawda jeszcze nie przetestowałem " na ostro " ale program kończy się normalnym wyjściem.

**Rozwiązanie dla potomnych: **

#include <Box2D/Box2D.h>

#include <vector>

class shape
{
public:
    shape(b2World&);
    ~shape();
    shape(const shape&);
    b2Body* getPhysical() const;
private:

  b2Body* physical;
};

b2Body* shape::getPhysical() const
{
    return physical;
}
shape::shape(const shape& sh)
{
   // pierwszy raz tworze konstruktor kopiujący jak coś nie tak proszę o feedback :)
    b2BodyDef bodyDef;
    b2Body* phy = sh.getPhysical();

    // w dokumentacji nie znalazłem zadniej funkcji typu getBodyDef()
    // wiec trzeba skopiować wszystko manualnie

    bodyDef.type = phy->GetType();
    bodyDef.position.Set(phy->GetPosition().x,phy->GetPosition().y );

    physical = phy->GetWorld()->CreateBody(&bodyDef);

}
shape::~shape()
{
    if(physical != nullptr) {
    physical->GetWorld()->DestroyBody(physical);
    physical = nullptr;
    }
}
shape::shape(b2World& world)
: physical(nullptr)
{

    b2BodyDef bodyDef;
    bodyDef.type = b2_dynamicBody;


    bodyDef.position.Set(1.0f, 1.0f);
    physical = world.CreateBody(&bodyDef);
    //skipping fixture etc..

}


int main()
{

b2Vec2 gravity(0.0f,10.0f);
b2World world(gravity, true);

std::vector<shape> objects;
objects.push_back(shape(world));


}

Temat można zaznaczyć jako rozwiązany. ~Złoty Krawiec

3
abbq napisał(a):

Problem rozwiązany! Męczyłem się z tym od paru dni

zerknij sobie jeszcze tutaj bo prawdopodobnie ze względów bezpieczeństwa powinieneś swoją klasę uzupełnić jeszcze. Ponadto zapamiętaj, że jeżeli w klasie jako składową przechowujesz wskaźnik to jest to odpowiednia przesłanka do tego aby zdefiniować jawnie konstruktory, destruktor oraz operator.

1 użytkowników online, w tym zalogowanych: 0, gości: 1