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

Odpowiedz Nowy wątek
2015-01-09 17:07
Złoty Krawiec
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::vector:
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

Dla ścisłości, gdy dodam jakąś pętle trzymającą program przy życiu crash następuje dopiero gdy nacisnę X, tzn. main() się skończy, lub też gdy sam na jakiś event próbuje usunąć element z vectora. + według dokumentacji funkcja nie zapisuje referencji tylko kopiuje object, co sprawdziłem dodając bodyDef do ciała klasy - problem pozostał. ~Złoty Krawiec - abbq 2015-01-09 17:31

Pozostało 580 znaków

2015-01-09 17:12
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.

Pozostało 580 znaków

2015-01-09 17:21
Złoty Krawiec
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?

Pozostało 580 znaków

2015-01-09 17:28
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.

edytowany 3x, ostatnio: n0name_l, 2015-01-09 17:43

Pozostało 580 znaków

2015-01-09 17:53
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

edytowany 2x, ostatnio: abbq, 2015-01-09 17:56

Pozostało 580 znaków

2015-01-10 05:59
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.


"..."
"odp"
"qtMaster"

Pozostało 580 znaków

Odpowiedz
Liczba odpowiedzi na stronę

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