2014-12-18 2 views
0

У меня есть пара несвязанных классов, работающих вместе с помощью прослушивателей. Они оба держать копию своего соответствующего виртуального интерфейса, упрощенной, как это:На пороге программы удаления UB-сервера/клиентской памяти

struct Base 
    { 
     struct IFormat 
     { 
      virtual int formatVal(string &) = 0; 
     }; 

     void addFmt(IFormat * fmt) 
     { 
      if (!fmts.contains(fmt)) 
       fmts.push_back(fmt); 
     } 

     void removeFmt(IFormat * fmt) 
     { 
      auto it = fmts.find(fmt); 
      if (it != fmts.end()) 
       fmts.erase(it); 
     } 

     int getValueFromFmts(string & input) 
     { 
      // this is getting called once in a while 
      int ret(0); 
      for (auto fmt : fmts) 
       ret += fmt->formatVal(input); 
     } 

     virtual ~Base() 
     { 
      // what now? something probably has a reference to me. 
     } 
    private: 
     vector<IFormat *> fmts; 

    }; 


    struct Editor : public Base::IFormat 
    { 
     Editor(Base * base) 
      : base(base) 
     { 
      base->addFmt(this); 
     } 

     ~Editor() 
     { 
      // base might be deleted! 
      base->removeFmt(this); 
     } 

     virtual int formatVal(string &) override { ... } 

    private: 
     Base * base; 
    }; 

Проблема, как указано в коде: Любой класс может быть удален в любой момент. Редактор может отменить регистрацию его форматирования, но если базовый класс сначала исчезнет, ​​все становится плохо. Это проблема, в которой у меня много мест (это GUI-код), поэтому мне понадобилось общее решение. Я понял, что все «базовые» классы могут быть получены из общего уведомления об исключениях (сервер) и реализовать механизм уведомления в качестве клиентов этого сервера. Я создал этот кусок кода, который работает великолепно:

template<typename Derived> 
class DestructionServer 
{ 
public: 
    typedef Derived type; 

    class ObjectProxy 
    { 
    public: 
     ObjectProxy(const Derived * serverToPresent) 
      : server(serverToPresent) 
     {} 

     bool operator == (const Derived * other) const 
     { 
      return server == other; 
     } 

     bool operator != (const Derived * other) const 
     { 
      return server != other; 
     } 

    private: 
     const Derived * server; 
    }; 

    class Client 
    { 
     friend class DestructionServer<Derived>; 
    public: 
     Client() 
      : server(nullptr) 
     { 

     } 

     typedef DestructionServer Server; 

     virtual ~Client() 
     { 
      if (server) 
       server->removeClientDestructor(this); 
     } 

     virtual void onObjectDestruction(const ObjectProxy & destroyedObject) = 0; 

    private: 
     void onDestruction(const Derived * derivedServer) 
     { 
      if (!server) 
       throw std::runtime_error("Fatal error: DestructionServer::Client has no server!"); 
      // derivedServer should be able to downcast to server, without conversion 
      if (derivedServer != server) 
       throw std::runtime_error("Fatal error: derivedServer does not derive from server!"); 

      // forget reference to server 
      server = nullptr; 
      // return an unmodifiable reference to the server 
      onObjectDestruction(derivedServer); 
     } 
     Server * server; 
    }; 


    void removeClientDestructor(Client * client) 
    { 
     auto it = std::find(clients.begin(), clients.end(), client); 
     if (it != clients.end()) 
     { 
      clients.erase(it); 
     } 
    } 

    void addClientDestructor(Client * client) 
    { 
     if (client && !std::contains(clients, client)) 
     { 
      clients.push_back(client); 
      // this only happens if a client is loaded into multiple servers 
      // or the client is trying to add itself multiple times to the same server. 
      if (client->server) 
       throw std::runtime_error("Fatal error: Client already has a server!"); 
      client->server = this; 
     } 
    } 

    virtual ~DestructionServer() 
    { 
     for (Client * client : clients) 
     { 
      // would like to use a dynamic_cast here to check the upcast, 
      // but it is not possible since 
      // ((Derived*)this) is actually deconstructed at this point... 
      // or can static_cast handle this? 
      // in effect this is UB, but it 'works' 
      if (const Derived * derivedServer = static_cast<const Derived *>(this)) 
      { 
       client->onDestruction(derivedServer); 
      } 
      else 
      { 
       // in fact, the typeid() shouldn't work as well, here? 
       throw std::runtime_error(
        std::string("Fatal error: ") + typeid(this).name() + 
        " doesn't derive from " + typeid(DestructionServer<Derived> *).name() 
        ); 
      } 
     } 

    } 
protected: 
    // make it impossible to construct this class without 
    // deriving from this class. 
    DestructionServer() {}; 

private: 
    std::vector<Client *> clients; 
}; 

Теперь я могу переписать пример, как это, и держать его полностью удалить безопасным:

struct Base : public DestructionServer<Base> 
    { 
     struct IFormat : public Client 
     { 
      virtual int formatVal(string &) = 0; 
     }; 

     void addFmt(IFormat * fmt) 
     { 
      if (!fmts.contains(fmt)) 
      { 
       addClientDestructor(fmt); 
       fmts.push_back(fmt); 
      } 
     } 

     void removeFmt(IFormat * fmt) 
     { 
      auto it = fmts.find(fmt); 
      if (it != fmts.end()) 
      { 
       fmts.erase(it); 
      } 
     } 

     int getValueFromFmts(string & input) 
     { 
      // this is getting called once in a while 
      int ret(0); 
      for (auto fmt : fmts) 
       ret += fmt->formatVal(input); 
     } 

     virtual ~Base() 
     { 
      // DestructionServer automatically notifies all 
      // objects that set a formatter to this class. 
     } 
    private: 
     vector<IFormat *> fmts; 

    }; 


    struct Editor : public Base::IFormat 
    { 
     Editor(Base * base) 
      : base(base) 
     { 
      base->addFmt(this); 
     } 

     ~Editor() 
     { 
      // base is now null if it is deleted 
      if(base) 
       base->removeFmt(this); 
     } 

     virtual int formatVal(string &) override { ... } 
     virtual void onObjectDestruction(const Base::ObjectProxy & object) 
     { 
      if (object == base) 
      { 
       // ok, base is destructed now. set it to null and go kill our self 
       base = nullptr; 
       delete this; 
      } 
     } 
    private: 
     Base * base; 
    }; 

Проблема

Если вы сделали это через стену кода и прочитали комментарии - спасибо, вы, вероятно, заметили, что я отбрасываю этот указатель из базового деконструктора в производный тип и вызывая уведомления. Зная полностью, что указатель, скорее всего, мусор (наполовину деконструированный), я разработал класс ObjectProxy, чтобы клиенты не использовали этот объект (хотя они все еще могут, если у них есть копия, очевидно).

Единственное, что позволяет ObjectProxy, - сравнить значение указателя на другое, того же типа (чтобы клиенты могли проверить, какой объект был разрушен), поэтому я не уверен, что это фактически неопределенное поведение - это никогда не разыскивает указатель.

Кроме того, может ли upcast когда-либо терпеть неудачу во время выполнения - мне нужен динамический_cast? Я почти уверен, что static_cast запрещает предоставление несвязанных типов шаблонов DestructionServer, поэтому неудачные типы будут терпеть неудачу во время компиляции.

Любые другие идеи для создания этой работы системы также приветствуется ...

+0

Вы, вероятно, хотите 'std :: set' вместо' std :: vector '. O (log N). – MSalters

+0

Да, возможно. Тем не менее оптимизация - это наименьшее из моих проблем. – Shaggi

ответ

2

выглядит как типичный случай для std::shared_ptr. Очевидно, вам нужно явно сбросить общие указатели, чтобы предотвратить циклические ссылки, или использовать std::weak_ptr в одном направлении. Нынешнее решение, похоже, страдает от чрезмерной сложности.

Вы даже можете взглянуть на причудливую технику. Вы можете иметь несколько shared_ptr разных типов, имеющих общий счет использования. Следовательно, shared_ptr<IFormat> может использовать счет использования с shared_ptr<Base>. Следовательно, вы можете передать либо указатель на код клиента, и даже пользователи shared_ptr<IFormat> сохранят Base в живых. А так как вы создали Base, чтобы получить IFormat, все становится тривиальным.

+0

Im пытается представить, как вы это сделаете - вы говорите, что редактор должен быть построен с использованием shared_pointer для базы? Обратите внимание, что полностью допустимо поведение, которое база разрушена перед редактором (и ее не нужно удалять) – Shaggi

+0

@Shaggi: Как бы разрушить базу, если у редактора все еще есть shared_ptr? Помните, что при использовании 'shared_ptr' вы никогда не называете' delete'. – MSalters

+0

Мои формулировки, вероятно, немного расплывчаты; Прости. _intended_, что базовый класс может быть удален в любой точке. Рассмотрите фактический случай, когда базой является произвольная кнопка управления /, которая отображает значение, а редактор является статической частью графического интерфейса, который может прикрепляться к элементам управления (которые производятся от базы). Цель редакторов - настроить элемент управления по-разному. Теперь пользователь может «закрыть», скрыть или сделать что-либо из элементов управления, что приведет к сбросу редактора. – Shaggi

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

  • Нет связанных вопросов^_^