2015-06-08 3 views
0

Я работаю над простой игрой с использованием C++ и Allegro. Я столкнулся с ошибкой времени выполнения Access violation относительно vectorstructs, которые содержат unique_ptrs до ALLEGRO_BITMAPs.Копирование вектора structs unique_ptrs

Вот моя декларация структуры.

struct Skin { 
    std::unique_ptr<ALLEGRO_BITMAP> img; 
    Skin(); 
    Skin(ALLEGRO_BITMAP*); 
    Skin& operator=(const Skin& s); 
    Skin(const Skin& s); 
}; 

И вот определения конструкторов в другом файле.

Skin::Skin() { 
    img.reset(); 
} 

Skin::Skin(ALLEGRO_BITMAP* bitmap) { 
    img.reset(bitmap); 
} 

Skin::Skin(const Skin& s) { 
    img.reset(s.img.get()); 
} 

Skin& Skin::operator=(const Skin& s) { 
    img.reset(s.img.get()); 
    return *this; 
} 

Вот код, который вызывается перед нарушением прав доступа.

generateBase(world, display.get()); 

Какой вызов вызывает эта функция.

void generateBase(World& world, ALLEGRO_DISPLAY* display) { 
    int x = TILESIZE - WIDTH; 
    int y = HEIGHT - TILESIZE; 
    int groundWidth = 3 * WIDTH - 2 * TILESIZE; 
    Point min{ x, y }; 
    Point max{ x + groundWidth, y + (int)TILESIZE }; 
    ALLEGRO_BITMAP* black = al_create_bitmap(groundWidth, TILESIZE); 
    ALLEGRO_BITMAP* white = al_create_bitmap(groundWidth, TILESIZE); 
    al_set_target_bitmap(black); 
    al_clear_to_color(al_map_rgb(0, 0, 0)); 
    al_set_target_bitmap(white); 
    al_clear_to_color(al_map_rgb(255, 255, 255)); 
    al_set_target_bitmap(al_get_backbuffer(display)); 
    std::cout << "Errors incoming!" << endl; 
    createPlayer(world, x, y, 0, 0, 5, vector <AABB> { AABB(min, max) }, vector <Skin> { Skin(black), Skin(white) }); 
    std::cout << "Did we make it?" << endl; 
} 

Эта функция, в свою очередь, вызывает эту функцию.

unsigned int createPlayer(World& world, int x, int y, float dx, float dy, float speed, vector<AABB>& mesh, vector<Skin>& imgs) { 
    unsigned int entity = newEntityIndex(world); 
    world.masks[entity].set(COMPONENT_TYPE); 
    world.masks[entity].set(COMPONENT_POINT); 
    world.masks[entity].set(COMPONENT_UNITVECTOR); 
    world.masks[entity].set(COMPONENT_SPEED); 
    world.masks[entity].set(COMPONENT_COLLISIONMESH); 
    world.masks[entity].set(COMPONENT_SKINLIST); 
    world.types[entity] = TYPE_PLAYER; 
    world.points[entity] = Point(x, y); 
    world.unitVectors[entity] = UnitVector(dx, dy); 
    world.speeds[entity] = Speed(speed); 
    world.collisionMeshes[entity].mesh = mesh; 
    cout << "Starting vector copy" << endl; 
    for (auto skin : imgs) { 
     world.skinLists[entity].imgs.push_back(move(skin)); 
    } 
    cout << "Ending vector copy" << endl; 
    return entity; 
} 

Это мой дед для unique_ptr.

namespace std { 
    template<> 
    class default_delete <ALLEGRO_BITMAP> { 
    public: 
     void operator()(ALLEGRO_BITMAP* ptr) { 
      cout << ptr << endl; 
      al_destroy_bitmap(ptr); 
     } 
    }; 
} 

Вот выход.

Errors incoming! 
Starting vector copy 
00AF9468 
00AF9468 

Когда я изменил мой createPlayer вызов в generateBase путем удаления Skin(white), выход изменен.

Errors incoming! 
Starting vector copy 
00799468 
Ending vector copy 
00799468 

Изменение выхода у меня немного озадачило, но мой самый большой вопрос в том, что мне нужно сделать, чтобы изменить о том, как скопировать мой vector из structs из unique_ptrs так, что я не пытаюсь удалить тот же указатель дважды.

Заранее благодарен!

+0

1) Вы уже говорили о [минимальные полные примеры] (http://stackoverflow.com/help/mcve), 2) ваш 'Skin :: operator =' нарушает правила 'std :: unique_ptr'. – Beta

ответ

3

Первое, что нужно понять - вы можете иметь только один объект std::unique_ptr, содержащий указатель на определенный объект. Ваш конструктор Skin(const Skin& s) нарушает этот принцип, в результате чего две копии unique_ptr. Если у вас есть объект, содержащий unique_ptr пользователей, вам необходимо выполнить одно из следующих действий:

  1. не имеет конструктор копирования или оператор присваивания.
  2. В операторе копирования и/или присваивании назначьте новую копию базового ресурса. Для дублирования ресурса потребуется позвонить al_clone_bitmap.

Во-вторых, когда вы держите ресурс в unique_ptr, вы хотите инициализировать unique_ptr в том же месте, где вы создаете ресурс.Например, вместо того, чтобы создать локальную переменную ALLEGRO_BITMAP* black, используйте следующую команду:

std::unique_ptr<ALLEGRO_BITMAP> black(al_create_bitmap(groundWidth, TILESIZE)); 

Поскольку этот код создает unique_ptr непосредственно из результата al_create_bitmap, вы хотите, чтобы удалить в Skin конструктор, который принимает ALLEGRO_BITMAP* и заменить его следующим образом:

Skin::Skin(std::unique_ptr<ALLEGRO_BITMAP>&& bitmap) 
    : img(bitmap) 
{ 
} 

Вы можете создать Skin, перемещая unique_ptr в него:

Skin(std::move(black)) 

Собирает все вместе, конструктор рабочей копии может выглядеть следующим образом. Это не особенно эффективно, но это безопасно.

Skin::Skin(const Skin& s) 
    : img(al_clone_bitmap(s.img.get())) 
{ 
} 
1

Проблема здесь:

Skin::Skin(const Skin& s) { 
    img.reset(s.img.get()); 
} 

Skin& Skin::operator=(const Skin& s) { 
    img.reset(s.img.get()); 

Вы крадете сырой указатель из одного unique_ptr и назначая его на другой. Теперь unique_ptr принадлежит к категории RAII. Они ожидают, что срок жизни объекта будет до тех пор, пока они будут живы. Когда вы сделаете это

img.reset(s.img.get()); 

Вы вынули указатель из одного unique_ptr и передан другой unique_ptr. Теперь unique_ptr1 считает, что ему принадлежит базовый объект, не подозревающий о том, что существует еще один уникальный_ptr2, который считает то же самое. Поэтому, когда они умрут, они с радостью освободят память, выделенную для _Ptr. Таким образом, ваш код будет в конечном итоге получать доступ к/освобождение памяти, которая уже освобождается первым уникальным_ptr, который умирает.
Now both unique_ptr believes that they own _Ptr

Вы должны либо переместить unique_ptr (таким образом, податливость собственности) или явно вызывая релиз s.img

Смежные вопросы