Сегодня задачка по С++, вопрос простой - что не так? Если хочется подумать самостоятельно, то сначала прочитайте код, высказывайтесь в комментариях, а потом читайте дальше.
#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"; }
};
Т.е. по мере разрушения у объектов менялись таблицы виртуальных функций и иногда одному из них везло, а второму не везло никогда.
Вот такой вот способ выстрелить себе в ногу с двух ̶р̶у̶к̶ потоков.