Arhn - архитектура программирования

Ошибка сегментации при использовании shared_ptr

Я создаю систему частиц, и я борюсь с тем, как структурировать свой код. Идея состоит в том, что пользователь может создать один или несколько объектов ParticleEmitter, которые передаются объекту ParticleManager через объект ofxCurlNoise.

Теперь я хочу, чтобы когда пользователь обновляет объекты ParticleEmitters, объект ParticleManager видел внесенные изменения. Поэтому я использовал общие указатели, но у меня возникают ошибки сегментации в разное время, независимо от того, использую ли я один ParticleEmitter (ошибка сегментации при запуске программы) или vector<ParticleEmitter> (ошибка сегментации при выходе из программы).

Что с этим не так? Есть ли шаблон проектирования для того, что я пытаюсь сделать?

ofApp.h

#include "ofxCurlNoise.h"

class ofApp : public ofBaseApp{

    // ParticleEmitter particleEmitter;
    vector<ParticleEmitter> particleEmitters;
    ofxCurlNoise curlNoise;

    public:
        void setup();

};

ofApp.cpp

#include "ofApp.h"

void ofApp::setup(){
    // This produces a segfault as soon as the program starts
    // particleEmitter.setup();
    // curlNoise.setup(particleEmitter, 1024*256);

    // This produces a segfault when the program exits
    ParticleEmitter emitter;
    emitter.setup();
    particleEmitters.push_back(emitter);
    curlNoise.setup(particleEmitters, 1024*256);    

}

ofxCurlNoise.h

#include "ParticleManager.h"

class ofxCurlNoise {    

    ParticleManager particleManager;

    public:
        void setup(ParticleEmitter& emitter, int n);
        void setup(vector<ParticleEmitter>& emitters, int n);

    private:
        void setup(int n);    

};

ofxCurlNoise.cpp

#include "ofxCurlNoise.h"

void ofxCurlNoise::setup(ParticleEmitter& emitter, int n){
    particleManager.addEmitter(shared_ptr<ParticleEmitter>(&emitter));
    setup(n);
}

void ofxCurlNoise::setup(vector<ParticleEmitter>& emitters, int n){
    for(auto& e : emitters){
        particleManager.addEmitter(shared_ptr<ParticleEmitter>(&e));
    }
    setup(n);
}

void ofxCurlNoise::setup(int n){
    particleManager.setup(n);
}

ParticleManager.h

#include "ParticleEmitter.h"

class ParticleManager{    

    vector<shared_ptr<ParticleEmitter>> emitters;

    public:
        void addEmitter(const shared_ptr<ParticleEmitter>& emitter);
        void setup(int n);
};

ParticleManager.cpp

#include "ParticleManager.h"

void ParticleManager::setup(int n){
    //...
}

void ParticleManager::addEmitter(const shared_ptr<ParticleEmitter>& emitter){
    emitters.push_back(emitter);
}

  • Цитирование руководящих принципов SO: вопросы, требующие помощи по отладке, должны включать желаемое поведение, конкретную проблему или ошибку и кратчайший код, необходимый для их воспроизведения в самом вопросе. Вопросы без четкой формулировки проблемы бесполезны для других читателей. 23.02.2015
  • @UlrichEckhardt Этот действительно содержит как код, необходимый для идентификации ошибки, так и достаточно четкое описание проблемы. Это не звездный вопрос, но я видел и хуже. 23.02.2015
  • В std::shared_ptr вы можете хранить только динамически созданные объекты (с помощью new)... Насколько я вижу, вы переходите к std::shared_ptr тому, что находится в vector<ParticleEmitter>... 23.02.2015
  • У него нет main(), у него куча разных классов и функций, так что обе части отсутствуют и лишние. Это противоречит цитируемому правилу @Angew. 23.02.2015
  • Да, извините, если я был недостаточно ясен. Это код для openFrameworks, ofApp::setup() вызывается при запуске программы. 23.02.2015
  • Я уже догадался об этом, но для таких рекомендаций есть причины. Если бы вы потратили время на то, чтобы сначала уменьшить проблему, вы бы обнаружили, что использование openFrameworks не имеет значения. Тогда вы смогли бы уменьшить проблему примерно до 20 строк кода, что, в свою очередь, помогло бы вам сосредоточиться на проблеме. Это основная стратегия решения проблем, которую вы должны изучить. Это также делает этот пост более понятным и полезным для других. 23.02.2015

Ответы:


1

std::shared_ptr работает не так. Вы создаете свои экземпляры ParticleEmitter в стеке, но std::shared_ptr используется для управления экземплярами, созданными в куче. В вашем коде, когда вы добавляете новый эмиттер в ParticleManager и заключаете его в общий указатель, эмиттер уничтожается при уничтожении вектора particleEmitters (когда, в свою очередь, уничтожается ваш экземпляр ofApp) и, таким образом, уничтожается независимо.

Когда экземпляр ofApp уничтожается, оба экземпляра ofxCurlNoise и particleEmitters уничтожаются (именно в таком порядке). Таким образом, ofxCurlNoise, в свою очередь, уничтожит particleManager, который управляет вашими общими указателями, который затем удалит ваши эмиттеры частиц (которые изначально были созданы в стеке). После всего, что сделано, вектор particleEmitters уничтожается, и система выполнения снова попытается уничтожить ваши эмиттеры частиц, что приведет к ошибке, которую вы видите.

Кроме того, общие указатели используются для моделирования семантики совместного владения, чего я не вижу в вашем случае использования. Я думаю, вам лучше либо использовать std::unique_ptr для управления экземплярами, созданными в куче, либо вообще не использовать умные указатели и создавать все в стеке (что вы уже почти делаете).

23.02.2015
  • +1, с небольшой придиркой: OP указывает shared_ptrs на элементы в vector, а не на локальные переменные. 23.02.2015
  • Хорошо, сейчас я понимаю. Таким образом, shared_ptr не является подходящим инструментом для этого. Но дело в том, что я бы не хотел использовать указатели на ParticleEmitters объекты в классе ofApp. Однако изменения, внесенные в ParticleEmitter в ofApp, должны быть видны в ParticleManager. Как я могу этого добиться? 23.02.2015
  • @ElieGnrd Сначала вам нужно уточнить семантику владения. Кому принадлежат излучатели частиц, т. е. кто должен нести ответственность за управление их жизненным циклом? 23.02.2015
  • ofApp владеет ParticleEmitters 23.02.2015
  • Вы можете создавать эмиттеры в куче и хранить их в векторе std::unique_ptrs в ofApp. Затем вы можете либо передать экземпляры как необработанные указатели, либо передать ссылку на вектор. Или вы можете создать их в стеке и управлять ими с помощью вектора и передать ссылку на вектор. 23.02.2015
  • Этот код предназначен для использования другими пользователями (это дополнение к фреймворку). Пользователи будут писать код только в классе ofApp, и я хочу, чтобы он был максимально простым (то есть не использовал указатели в ofApp). Итак, есть ли способ сделать это и сохранить код в ofApp как есть? 23.02.2015
  • Да, передайте свой вектор эмиттеров по ссылке конструктору ParticleManager и сохраните ссылку там. Но вы должны быть уверены, что ParticleManager будет уничтожен раньше, чем ofApp. 23.02.2015
  • Давайте продолжим обсуждение в чате. 23.02.2015

  • 2

    Вы никогда не должны создавать shared_ptr из обычного указателя, как здесь:

     shared_ptr<ParticleEmitter>(&e)
    

    Это дважды пытается освободить ParticleEmitter. Один раз, когда вектор, содержащий объекты ParticleEmitter, выходит за пределы области действия, и один раз, когда за пределы области действия выходит объект shared_ptr.

    23.02.2015
  • Встроенный указатель, например. ParticleEmitter*, а не умный указатель. 23.02.2015
  • Тогда вам нужен обычный указатель при создании shared_ptr, другого способа его использования нет 24.02.2015
  • Лучшей практикой является использование make_shared() для создания файла shared_ptr. Но вы правы, я мог бы быть более точным. 24.02.2015

  • 3
    void ofxCurlNoise::setup(vector<ParticleEmitter>& emitters, int n){
       for(auto& e : emitters){
        particleManager.addEmitter(shared_ptr<ParticleEmitter>(&e));
      }
      setup(n);
    }
    

    Похоже, вы создаете общий указатель из выделенных объектов «стека». Вы должны построить объекты ParticleEmitter, используя new или make_shared<ParticleEmitter>, но происходит следующее: когда вектор изменяется и ParticleEmitter копируется в новое место, shared_ptr<ParticleEmitter> указывает на неверный адрес. Кроме того, когда вектор выходит за пределы области действия, элементы уничтожаются.

    23.02.2015
  • e не является объектом стека, это ссылка на объект, управляемый std::vector 23.02.2015
  • Его нет в стеке, но похоже, что он есть. Помимо уничтожения, контролируемого вектором, он также перемещается в другую ячейку памяти при изменении размера вектора. 23.02.2015

  • 4

    При передаче указателя на shared_ptr последний становится его владельцем и управляет им. Когда вы передаете указатель на объект, уже управляемый std::vector, он рано или поздно будет удален дважды, что, конечно, не может работать. shared_ptr должен быть передан указатель, которым еще не управляет другой класс.

    Итак, вместо:

    shared_ptr<ParticleEmitter>(&e)
    

    Вы должны создать копию объекта ParticleEmitter

    Использовать:

    shared_ptr<ParticleEmitter>(new ParticleEmitter(e))
    

    Или лучше:

    std::make_shared<ParticleEmitter>(e)
    

    Оба метода требуют, чтобы ParticleEmitter имел конструктор копирования.

    Если ParticleEmitter является тяжелым классом, и вы хотите избежать его глубокой копии, тогда он должен реализовать семантику перемещения (конструктор перемещения) и использовать:

    std::make_shared<ParticleEmitter>(std::move(e))
    
    23.02.2015
    Новые материалы

    Коллекции публикаций по глубокому обучению
    Последние пару месяцев я создавал коллекции последних академических публикаций по различным подполям глубокого обучения в моем блоге https://amundtveit.com - эта публикация дает обзор 25..

    Представляем: Pepita
    Фреймворк JavaScript с открытым исходным кодом Я знаю, что недостатка в фреймворках JavaScript нет. Но я просто не мог остановиться. Я хотел написать что-то сам, со своими собственными..

    Советы по коду Laravel #2
    1-) Найти // You can specify the columns you need // in when you use the find method on a model User::find(‘id’, [‘email’,’name’]); // You can increment or decrement // a field in..

    Работа с временными рядами спутниковых изображений, часть 3 (аналитика данных)
    Анализ временных рядов спутниковых изображений для данных наблюдений за большой Землей (arXiv) Автор: Рольф Симоэс , Жильберто Камара , Жильберто Кейрос , Фелипе Соуза , Педро Р. Андраде ,..

    3 способа решить квадратное уравнение (3-й мой любимый) -
    1. Методом факторизации — 2. Используя квадратичную формулу — 3. Заполнив квадрат — Давайте поймем это, решив это простое уравнение: Мы пытаемся сделать LHS,..

    Создание VR-миров с A-Frame
    Виртуальная реальность (и дополненная реальность) стали главными модными терминами в образовательных технологиях. С недорогими VR-гарнитурами, такими как Google Cardboard , и использованием..

    Демистификация рекурсии
    КОДЕКС Демистификация рекурсии Упрощенная концепция ошеломляющей О чем весь этот шум? Рекурсия, кажется, единственная тема, от которой у каждого начинающего студента-информатика..