Допустимо использовать это в функции, которая вызывается с использованием общего указателя - PullRequest
0 голосов
/ 08 июня 2018

У меня есть вопрос об использовании общих указателей и многопоточном программировании на C ++.Если я вызываю функцию-член с использованием объекта общего указателя этого класса в потоке A, тогда допустимо использовать атрибут this внутри класса и передать его в функцию обратного вызова, которая будет вызвана из потока B. С моей точки зрения, если потокA завершает свою работу, тогда общий указатель будет истек, и это будет недействительным.

Допустимо использовать shared_from_this и приведено к void *?

Здравствуйте, вот код.Этот код будет генерировать ошибку сегментации, верно?

#include <memory>
#include <iostream>
#include <thread>
#include <unistd.h>
#include "ClassB.h"
class B;
class A
{
public:
        A()= default;

        void foo(void * ptr)
        {
            std::cout <<"enter in foo" <<std::endl;
            sleep(1);
            std::cout << "Thread wake up" << std::endl;
            B *pB = reinterpret_cast<B*>(ptr);
            pB->x = 5;
        }
};

void B::bar()
{
    std::cout << "enter in bar" << std::endl;
    void * ptr = reinterpret_cast<void *>(this);
    auto t2= std::thread(&A::foo,A(),ptr);
    t2.detach();
}

void threadTask()
{
    std::cout << "ThreadTask" << std::endl;
    std::shared_ptr<B> psharedB = std::make_shared<B>();
    auto t1 = std::thread(&B::bar,psharedB);
    t1.detach();
}

int main()
{
    auto t = std::thread(threadTask);
    t.join();
    sleep(20);

    return 0;
}

Спасибо, Джордж

Ответы [ 2 ]

0 голосов
/ 14 июня 2018
std::shared_ptr<B> psharedB = std::make_shared<B>();
auto t1 = std::thread(&B::bar,psharedB);

Я не адвокат по языку, но я думаю, что приведенный выше код создаст экземпляр класса B, на который указывает общий указатель psharedB, а затем создаст экземплярstd::thread передача по значению указатель функции на метод B::bar в качестве первого параметра и передача по значению a копирование из psharedB в качестве второгопараметр.

Поскольку копия psharedB передается конструктору thread ( … ), количество ссылок общего указателя увеличивается до двух.Вы можете исследовать это, добавив следующий код в threadTask, чтобы проверить значение psharedB.use_count() после создания нового потока…

auto t1 = std::thread(&B::bar, psharedB);
// show use count is two not one
std::cout << "psharedB use count: " << psharedB.use_count() << std::endl;

… документация предупреждает, что значение, возвращаемое use_count равно "«приблизительно» в многопоточной среде, но в данном конкретном случае это должно быть точно.

Когда threadtask выходит, psharedB выходит за рамки.Счетчик использования общего указателя уменьшается деструктором общего указателя, но экземпляр объекта B сохраняется, поскольку копия psharedB, переданная в поток t1, все еще существует, поэтому счетчик использования равен единице, а не нулю.

В B::bar переменная this представляет собой обычный обычный необработанный указатель B *.Под обложками он был извлечен из умного указателя, переданного в поток t1, но что касается логики в B::bar, мы этого не знаем.Таким образом, когда этот указатель приведен к void *, он вообще не влияет на счетчик использования общего указателя.

Один из способов изучить это - установить точку останова на reinterpret_cast в B::bar и дойти до этой точки.Запись в стеке вызовов непосредственно под точкой останова будет логикой для вызова B::bar с копией psharedB.Отладчик должен позволить вам увидеть общий указатель и изучить его счетчик использования, который будет равен единице, если threadTask уже завершен, или двум, если threadTask еще не завершен.

Перейдите через reinterpret_cast и обратите внимание, что счетчик использования общего указателя не меняется.

Когда возвращается B::bar, поток t1, запущенный threadTask, завершается.Фактически копия psharedB, переданная этому потоку, выходит из области видимости и уничтожается.Счетчик использования общего указателя обнуляется, и экземпляр объекта B, первоначально созданный threadTask, также уничтожается.

В этот момент значение ptr, переданное foo, является указателем на удаленныйэкземпляр объекта.Любая попытка разыменования этого значения ptr внутри foo приведет к неопределенному поведению.Если вам повезет, это вылетит из вашей программы.Если вам не повезло, кажется, что разыменование работает, и вам будет сложно отследить сбой позже при выполнении вашей программы.Или какое-то странное поведение, которое случается иногда и очень трудно воспроизвести.

Еще хуже, в зависимости от , когда поток t1 уничтожен, и, таким образом, , когда B Экземпляр объекта уничтожен, переменная ptr в foo может быть действительным указателем B* для некоторых из foo исполнений и недопустимым указателем для остальных из foo.

Допустимо использовать shared_from_this и привести его к void *?

Ваши классы A и B не являются производными от std::enable_shared_from_this<T>, поэтому в вашемНапример, нельзя вызывать std::shared_from_this в любом классе.

Если вы действительно создали shared_ptr<B> экземпляр, вы обнаружите, что не можете привести его к void *.Попробуйте добавить auto temp = reinterpret_cast<void *> (psharedB); к threadTask.Вы обнаружите ошибку компилятора, потому что приведение является недопустимым преобразованием.

Допустимо ли использовать это в функции, которая вызывается с использованием общего указателя

Да, это действительно - в рамках определенных ограничений.Ваш метод B::bar, вызванный из threadTask, является функцией, которая вызывается с использованием общего указателя.Вы можете использовать this в пределах B::bar.Но вы не получите никакой специальной возможности для this только потому, что она была вызвана с помощью общего указателя.Что касается B::bar, то он мог быть вызван с использованием необработанного указателя, подобного этому ...

B* p = new B();
p->bar();

Используете ли вы совместно используемый указатель или необработанный указатель - недопустимо передавать this или void * версию this в другую функцию, например A::foo, без гарантии того, что экземпляр указывает на указательЗначение по-прежнему будет действительным, когда A::foo разыменует этот указатель.

Если вы используете shared_ptr<T> для управления сроком службы объекта, то в идеале вы хотите передать A::foo параметр shared_ptr<B>.Таким образом, объект поддерживается живым с помощью shared_ptr, переданного A::foo, даже если все другие общие указатели уничтожены.

0 голосов
/ 08 июня 2018

Если объект размещен в куче (используя new), он не становится недействительным, если поток завершается.shared_pointer хранит указатель на экземпляр в куче и один указатель на переменную count.Если копируется shared_pointer, переменная count увеличивается, если вызывается ее деструктор, она уменьшается.Деструктор вашего объекта вызывается разделяемым указателем, если счетчик получает 0, то есть, если shared_pointer s больше не указывает на объект.shared_pointer должно сохранять потоки, даже если это требует значительных ресурсов.Важно знать, что если вы передаете указатель на shared_pointer, то shared_pointer теперь отвечает за управление экземпляром, однако это никак не влияет на любое другое использование указателя.В результате вы не должны вызывать delete на других указателях на объект.Поэтому это не должно вызывать никаких конфликтов, если вы выделяете объект с помощью new.

Редактировать:

Это не должно работать, так как shared_pointer будет уничтожен, так как поток отсоединен иобласть действия слева и как count=1 B должны быть уничтожены.Отсоединение потока всегда плохая идея.

...