Умные указатели и это в длительном методе

есть связанные вопросы, такие как умные указатели + это считается вредным?, но они не обрабатывают мои кейс. Все эти вопросы касаются раскрытия необработанного указателя this в случае подсчета ссылок интеллектуальными указателями. Однако моя проблема не в том, что я выставляю this, а использую его только в методе, но, возможно, в течение более длительного периода времени.

Рассмотрим следующий код:

class X{
    void foo(){...} //Performs some long running task
}

shared_ptr<X> x;

void bar(){
    x->foo();
}

Итак, некоторый код вызывает метод foo объекта x. Учтите, что единственная разумная ссылка на экземпляр за x - это shared_ptr x. Теперь foo выполняет некоторую длительную задачу, вызывая другие функции и прочее.

Теперь представьте, что во время выполнения foo другой поток, обработчик сигнала или даже рекурсивный вызов в foo изменяет или очищает ссылку x. Это вызовет удаление объекта. Теперь указатель this в стеке вызовов foo указывает на удаленный объект. Поэтому дальнейшее выполнение кода в foo приведет к непредсказуемым результатам.

Как этого избежать? Я имею в виду, что всякий раз, когда выполняется метод объекта, который обрабатывается с помощью подсчета ссылок, существует опасность того, что какой-то код может очистить ссылки на объект, и вызов метода завершится ошибкой или приведет к странным результатам. Проблема заключается в семантике: подсчет ссылок считает, что ссылки на объект больше нет, и поэтому удаляет его, но это неверно - ссылка все еще существует, но, к сожалению, это не умный указатель.

Я знаю, что есть такие вещи, как enable_shared_from_this, но должен ли я ВСЕГДА переписывать все методы объектов, которые потенциально могут быть подсчитаны по ссылкам, чтобы сначала получить общий указатель от this, а затем использовать этот указатель, чтобы избежать этой проблемы? Это загромождает весь код метода и, кроме того, все равно может выйти из строя: если событие очищается x после того, как был сделан вызов foo, но до того, как будет выполнен первый оператор в методе (который получает общий экземпляр) (возможно, другой поток) , то снова произойдет сбой.

Итак, общий вопрос: как я могу быть в безопасности при использовании любого типа интеллектуальных указателей во время вызова метода к управляемому объекту? Как я могу гарантировать, что указатель this внутри метода не станет недействительным? Является ли переписывание всех методов для использования enable_shared_from_this в их первом операторе хорошим решением?

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


person gexicide    schedule 24.08.2012    source источник


Ответы (2)


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

Что касается рекурсивных вызовов методов: да, вы должны установить правило, что всякий раз, когда вы вызываете метод через общий указатель, который может привести к другим общим указателям на очищаемый объект, у вас будет копия общего указатель локально. Обычно это не должно быть проблемой, если вы передаете в функции общие указатели по значению.

Единственный случай, когда я вижу, что это может быть проблемой, - это то, что в вашем коде указано, что x является членом данных класса:

class C {
    shared_ptr<X> x;
public:
    void method1() { x.reset; }
    void method2() {
        x->foo();
    }
};

В этом случае, если возможно, что method1 может быть вызван из X::foo, вам нужно будет взять локальную копию x. Это только проблема, когда вы храните общие указатели в классе, и влияет только на методы этого класса.

person ecatmur    schedule 24.08.2012

Делитесь заботой: получите новую долю переменной!

shared_ptr<X> x;

void bar()
{
    shared_ptr<X> my_x = x;
    my_x->foo();
}
person Kerrek SB    schedule 24.08.2012
comment
Да, но тогда, когда я работаю с любыми интеллектуальными указателями, мне всегда нужно сначала создавать локальную копию. Это не будет загромождать код метода объекта, но может сильно загромождать весь клиентский код ... - person gexicide; 24.08.2012
comment
@gexicide: Не зря он называется общим указателем. Если вы не хотите делиться ресурсом, вам нужно изменить свой дизайн. - person Kerrek SB; 24.08.2012
comment
Итак, является ли это разумным общим правилом для использования интеллектуальных указателей: всякий раз, когда вызывается какой-либо метод через интеллектуальный указатель - если этот указатель является полем в объекте или глобальной переменной - сначала сделайте локальную копию - person gexicide; 24.08.2012
comment
@gexicide: Нет общего правила. Однако, если вы хотите, чтобы объект существовал до тех пор, пока у него есть потребители, вы можете реализовать это управление временем жизни с помощью shared_ptr, и каждый потребитель должен иметь свою собственную копию указанного общего указателя. - person Kerrek SB; 24.08.2012
comment
Я хотел бы иметь правило. Если я уважаю это, мой код может быть слишком оборонительным, но, по крайней мере, мне больше не нужно заботиться об этой проблеме. Моя проблема очень общая, есть ли общее правило, которое могло бы ее решить? Вышеупомянутый, кажется, легко решает. - person gexicide; 24.08.2012
comment
@gexicide: Общее правило - часть ваших общих дизайнерских навыков. Во всяком случае, я бы сказал, подумайте о времени жизни объекта, владении и ответственности. Общие указатели - это один из многих инструментов в вашем наборе инструментов, которые могут подходить или не подходить для реализации заданной цели дизайна. - person Kerrek SB; 24.08.2012