«Умные» указатели в C 98

#c

Вопрос:

У меня есть EventEmitter и EventHandler объект. Вы EventHandler* добавляете EventEmitter s в вектор обработчиков s. Это позволяет любому объекту, расширяющему обработчик, вызывать его через общий интерфейс через излучатель событий.

Теперь проблема возникнет, когда EventHandler s решит уничтожить без осознания этого программистом (обычно копируют операторы ctor и=) и EventEmitter в конечном итоге вызовет его, что приведет к сбою программы.

Первая идея состоит в том, чтобы предоставить EventHandler ссылку на его излучатель, чтобы он мог вызывать функцию отсоединения во время уничтожения. Но теперь мы должны учитывать, что эмиттер события решает умереть, и в любое время после этого может быть вызван деструктор обработчиков. Мы просто сдвинули проблему с мертвой точки.

Это звучит как очень распространенная проблема с указателями, которая, я бы не сомневался, была решена в C 11 или boost, но у меня нет доступа ни к одному из них. Существует ли общий макет для системы интеллектуальных указателей, который мог бы решить эту проблему в C 98?

Немного кода для иллюстрации

 // Example program
#include <iostream>
#include <string>
#include <vector>

class Handler {
public:
    std::string msg;
    Handler(std::string msg):msg(msg){}
    void Run(){
        std::cout << msg << std::endl;
    }
};

class Emitter {
public:
    std::vector<Handler*> handlers;
    void Attach(Handler *handler){
        handlers.push_back(handler);   
    }
    void Detach(Handler *handler){
        // find the handler, remove
    }
    void Emit(){
        for(size_t i = 0; i < handlers.size(); i  ){
            std::cout << "Calling a handler" << std::endl;
            handlers[i]->Run();   
        }
    }
};

int main()
{
    Emitter emitter;
    
    Handler handler1("handler1");
    emitter.Attach(amp;handler1);
    
    // Uh oh, attached, then out of scope
    {
        Handler handler2("handler2");
        emitter.Attach(amp;handler2);
    }
    
    emitter.Emit();
        
}
 

Выход

 Calling a handler                                                                                                                                           
handler1                                                                                                                                                    
Calling a handler    
 

Комментарии:

1. auto_ptr это слабое unique_ptr место в старых стандартных библиотеках C . Может быть, все-таки лучше свернуть свой собственный. Это гораздо сложнее, чем кажется на первый взгляд. С учетом сказанного, если вы используете его только для своего конкретного случая использования, а не в общем, это должно быть нормально.

2. Именно это я и собираюсь сделать. Просто сделай так, чтобы это работало на этих двух парней. Так что же им делать? Указывать друг на друга? У вас есть какая-то система подсчета ссылок?

3. Интеллектуальные указатели C 11 используют подсчет ссылок. Они даже потокобезопасны (с использованием атомики).

4. реализация boost-это afaik только для заголовков, для этих конкретных модулей нет двоичных файлов. Является ли ваш компилятор или платформа настолько странными, что вы не можете позаимствовать вдохновение из их реализации C 98 shared_ptr или использовать его напрямую? Тогда каковы шансы, что ваша реализация не будет работать или не будет разрешена проверкой кода или будет иметь недостатки в крайнем случае?

5. Хорошая идея! Я посмотрю, смогу ли я интегрировать его.

Ответ №1:

Хорошо, вот что я придумал. Общий указатель, у которого есть счетчик ссылок. Два объекта, ну, разделяют доступ к этому указателю. Когда они умрут, если уменьшить количество ссылок до 0, они удалят общий указатель. Это не должно произойти больше одного раза. По крайней мере, в моих тестах.

 // Example program
#include <iostream>
#include <string>
#include <vector>

class SharedPointer {
    public:
        SharedPointer()
            :ref_count(0)
        {
        }
        int ref_count;
};

class Handler {
public:
    SharedPointer *shared_pointer;
    std::string name;
    Handler(std::string name)
        :shared_pointer(NULL),
        name(name)
    {
        std::cout << "Constructing " << name << std::endl;
    }
    ~Handler(){
        std::cout << "Destructing " << name << std::endl;
        // Emitter is still alive
        if(shared_pointer amp;amp; shared_pointer->ref_count){
            shared_pointer->ref_count--;
            if(!shared_pointer->ref_count){
                delete shared_pointer;
                std::cout << "- - Emitter is dead, so deleting shared ptr " << std::endl;
            }
            else {
                std::cout << "- - Emitter is still alive, so leaving shared ptr " << std::endl;
            }
        }
    }
    void Run(){std::cout<<"Running"<<std::endl;}
};

class SmartHandler {
  public:
    Handler *handler;
    SharedPointer *shared_pointer;
    SmartHandler(Handler *handler, SharedPointer *shared_pointer)
        :handler(handler),
         shared_pointer(shared_pointer)
    {
        handler->shared_pointer = shared_pointer;
        handler->shared_pointer->ref_count  ;
    }
};

class Emitter {
public:
    std::vector<SmartHandler> handlers;
    ~Emitter(){
        for(size_t i = 0; i < handlers.size(); i  ){
            std::cout << "Removing a handler" << std::endl;
            if(handlers[i].shared_pointer amp;amp; handlers[i].shared_pointer->ref_count){
                handlers[i].shared_pointer->ref_count--;
                if(!handlers[i].shared_pointer->ref_count){
                    delete handlers[i].shared_pointer;
                    std::cout << "- - Handler is dead, so deleting shared ptr " << std::endl;
                }
                else {
                    std::cout << "- - " << handlers[i].handler->name << " is still alive, so leaving shared ptr " << std::endl;
                }
            }
        }
    }
    void Attach(Handler *handler){
        SharedPointer *shared_pointer = new SharedPointer();
        shared_pointer->ref_count  ;
        SmartHandler smart_handler(handler, shared_pointer);
        handlers.push_back(smart_handler);  
    }
    void Detach(Handler *handler){
        // find the handler, remove
    }
    void Emit(){
        for(size_t i = 0; i < handlers.size(); i  ){
            if(handlers[i].handler amp;amp; handlers[i].shared_pointer->ref_count > 1){
                std::cout << "Calling Run() for handler " << handlers[i].handler->name << std::endl;
                handlers[i].handler->Run();
            }
            else{
                std::cout << "This handler appears to be dead" << std::endl;   
            }
        }
    }
};

int main()
{
    Handler h_scope_1("h_scope_1");
    {
        Emitter emitter;
        emitter.Attach(amp;h_scope_1);
        
        Handler h_scope_2("h_scope_2");
        emitter.Attach(amp;h_scope_2);
        
        {
            Handler h_scope_3("h_scope_3");
            emitter.Attach(amp;h_scope_3);
        }
        
        emitter.Emit();
    }
}
 

Выход

 Constructing h_scope_1                                                                                                                                        
Constructing h_scope_2                                                                                                                                        
Constructing h_scope_3                                                                                                                                        
Destructing h_scope_3                                                                                                                                         
- - Emitter is still alive, so leaving shared ptr                                                                                                             
Calling Run() for handler h_scope_1                                                                                                                           
Running                                                                                                                                                       
Calling Run() for handler h_scope_2                                                                                                                           
Running                                                                                                                                                       
This handler appears to be dead                                                                                                                               
Destructing h_scope_2                                                                                                                                         
- - Emitter is still alive, so leaving shared ptr                                                                                                             
Removing a handler                                                                                                                                            
- - h_scope_1 is still alive, so leaving shared ptr                                                                                                           
Removing a handler                                                                                                                                            
- - Handler is dead, so deleting shared ptr                                                                                                                   
Removing a handler                                                                                                                                            
- - Handler is dead, so deleting shared ptr                                                                                                                   
Destructing h_scope_1                                                                                                                                         
- - Emitter is dead, so deleting shared ptr