Сегодня задачка по С++, вопрос простой - что не так? Если хочется подумать самостоятельно, то сначала прочитайте код, высказывайтесь в комментариях, а потом читайте дальше.
#include <iostream>
#include <memory>
#include <thread>

class Base{
public:
virtual ~Base() {
thread.join();
}

void proc() {
thread = std::thread(&Base::foo, this);
}

virtual void foo() {
std::cout << bar() << std::endl;
}

virtual std::string bar() = 0;

std::thread thread;
};

class Child : public Base
{
public:
std::string bar() override { return "Child"; }
};

class Derived : public Base
{
public:
std::string bar() override { return "Derived"; }
};

int main()
{
auto c = std::make_unique<Child>();
auto d = std::make_unique<Derived>();
c->proc();
d->proc();
}

Собираем g++ -O3 -pthread main.cpp и программа падает на вызове чисто виртуальной функции. Наша редакция прибегла к помощи специалистов и путем нехитрых манипуляций внесла изменения в код
virtual std::string bar() { return "Base"; };

Теперь вывод, казалось бы, должен быть очевиден, но нет:
Derived
Base

Перебрав несколько компиляторов и окружений, все таки стало понятно, что бывает и ожидаемый вариант
Base
Base


И я интересуюсь: "Где мое позднее связывание, ̶Л̶е̶б̶о̶в̶с̶к̶и̶ ?"
На что получаю резонное замечание от привлеченных специалистов из крупных международных компаний: "А, собственно, почему оно должно работать? Ты в деструкторе по факту вызываешь виртуальную функцию, наследник уже мертв в этот момент".

class Base{
public:
virtual ~Base() = default;
void wait() { thread.join(); }
...
};

class Child : public Base
{
public:
~Child() { wait(); }
std::string bar() override { return "Child"; }
};

class Derived : public Base
{
public:
~Derived() { wait(); }
std::string bar() override { return "Derived"; }
};

Т.е. по мере разрушения у объектов менялись таблицы виртуальных функций и иногда одному из них везло, а второму не везло никогда.

Вот такой вот способ выстрелить себе в ногу с двух ̶р̶у̶к̶ потоков.

Comments

Be the first to add a comment