Какова стоимость вызова функции-члена через общий указатель?

Часто утверждается, что разыменование интеллектуального указателя не оказывает заметного влияния на производительность. (Например, здесь: Производительность интеллектуального указателя C)

Теперь мне интересно, так ли это на самом деле. Я понимаю, что это может быть, если операции над объектом, на который ссылаются, являются атомарными.

Думая о коде на основе этих фрагментов:

class Worker
{
    [...]
    public:
        void setDataProvider( shared_ptr<DataProvider> p )
        {
            m_provider = p;
        }

        void doWork()
        {
            [...]
            for(;;) {
                int d = m_provider->getSomeData();

                [ do something with d ]
            }
        }

    private:

        shared_ptr<DataProvider>   m_provider;
};

doWork() будет выполняться постоянно, и время от времени setDataProvider() вызывается из второго потока. Вполне нормальный сценарий использования умного указателя.

По общему мнению, setDataProvider() требует дополнительных затрат, так как счетчик ссылок, защищенный блокировкой, должен быть изменен, а m_provider->getSomeData() — нет. Говорят, что это сравнимо с обычным разыменованием указателя, по крайней мере, не требуется дорогостоящая блокировка.

Но как это может работать? Допустим, getSomeData() не является атомарной операцией, в ней может быть некоторая логика и заметное время выполнения.

Как *m_provider защищен от удаления во время выполнения getSomeData()? Класс может быть единственным владельцем объекта. Перезапись m_provider уменьшит счетчик ссылок указателя на единицу. Либо m_provider->getSomeData() временно должен поднять счетчик ссылок, либо объект защищен от удаления в противном случае, пока выполняется getSomeData().

В обоих случаях необходим дорогостоящий механизм синхронизации/блокировки.

дополнение: в примере я использовал стандартное число shared_ptr, так как в целом меня это интересует. Однако в реальном коде используется QSharedPointer, поэтому меня это особенно интересует. Я наивно полагал, что оба будут иметь одинаковую безопасность потоков, что может быть неверным.


person philipp    schedule 10.08.2015    source источник
comment
В setDataProvider() назначение общего указателя не является атомарным. Вам по-прежнему нужно использовать мьютекс, чтобы убедиться, что вы не читаете во время записи или не выполняете двойную запись. См. этот ответ для получения дополнительной информации: stackoverflow.com/a/31255038/4342498   -  person NathanOliver    schedule 10.08.2015
comment
Верно ли это и для QSharedPointer? В документации Qt просто говорится, что QSharedPointer и QWeakPointer являются потокобезопасными и работают атомарно со значением указателя.   -  person philipp    schedule 10.08.2015
comment
[Форматирование]: это обратная кавычка `, которая должна использоваться для встроенного форматирования code. звездочка * для курсива   -  person Jarod42    schedule 10.08.2015
comment
@philipp Не будучи знакомым с Qt, я не могу дать вам определенный ответ, но документ говорит, что доступ к значению указателя является потокобезопасным, но это [...]provide no guarantee about the object being pointed to.   -  person NathanOliver    schedule 10.08.2015
comment
@Натан: Спасибо! Ты прав. Когда я впервые прочитал это предложение, я подумал, что это означает, что просто умные указатели не делают ваш объект потокобезопасным автоматически. Но на самом деле это также относится к этому ... Только что посмотрел код Qt, и безопасность не применяется. Использование -› на указателе просто передает вызов объекту.   -  person philipp    schedule 10.08.2015