Как исправить чистую виртуальную функцию, называемую ошибкой времени выполнения?

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

Базовый класс:

TailFileManager::TailFileManager(const std::string &filename, const int fileOpenPeriod_ms)
: m_Stop(false)
{
    m_WorkerThread.reset(new boost::thread(boost::bind(&TailFileManager::TailFile, this, filename, fileOpenPeriod_ms)));
}

TailFileManager::~TailFileManager()
{
    m_Stop = true;
    m_WorkerThread->join();
}

void TailFileManager::TailFile(const std::string &filename, const int fileOpenPeriod_ms)
{
    std::ifstream ifs(filename.c_str());

    while (! ifs.is_open())
    {
        boost::this_thread::sleep(boost::posix_time::milliseconds(fileOpenPeriod_ms));
    ifs.open(filename.c_str());
    }

    ifs.seekg(0, std::ios::end);

    while (! m_Stop)
    {
        ifs.clear();

        std::string line;

        while (std::getline(ifs, line))
        {
            OnLineAdded(line);
        }

        OnEndOfFile();
    }

    ifs.close();
}

Производный класс:

ETSLogTailFileManager::ETSLogTailFileManager(const std::string &filename, const int heartbeatPeriod_ms)
: TailFileManager(filename, heartbeatPeriod_ms),
  m_HeartbeatPeriod_ms(heartbeatPeriod_ms),
  m_FoundInboundMessage(false),
  m_TimeOfLastActivity(0)
{
}

ETSLogTailFileManager::~ETSLogTailFileManager()
{
}

void ETSLogTailFileManager::OnLineAdded(const std::string &line)
{
    // do stuff...
}

void ETSLogTailFileManager::OnEndOfFile()
{
    // do stuff...
}

person Andrew    schedule 27.01.2013    source источник
comment
Вы не можете вызывать виртуальные функции из деструктора. Что ж, можете, но результат будет не таким, как вы ожидали. Так что просто не делай этого.   -  person n. 1.8e9-where's-my-share m.    schedule 27.01.2013
comment
вы можете разместить объявления класса? Какие виртуальные функции?   -  person Emanuele Paolini    schedule 27.01.2013
comment
почему бы не попробовать отладить код?   -  person Cheers and hth. - Alf    schedule 27.01.2013
comment
@ n.m: ваш комментарий в корне неверен. виртуальные функции можно вызывать из конструкторов и деструкторов. и в отличие от таких языков, как Java и C #, в C ++ это безопасно (и делает именно то, что я ожидаю, но, возможно, не то, что ожидает тот, кто не знает язык).   -  person Cheers and hth. - Alf    schedule 27.01.2013
comment
В этом коде, похоже, нет вызовов функций-членов объекта из его деструктора.   -  person n. 1.8e9-where's-my-share m.    schedule 27.01.2013
comment
@ Cheersandhth.-Alf: Вы можете это сделать, если такой код не должен проходить мою проверку кода. Это не пройдет, потому что это нарушает принцип наименьшего удивления.   -  person n. 1.8e9-where's-my-share m.    schedule 27.01.2013
comment
@ Cheersandhth.-Alf: хотя, если вы вызываете виртуальную функцию по ее квалифицированному имени и объясняете почему в комментарии, меня это устраивает.   -  person n. 1.8e9-where's-my-share m.    schedule 27.01.2013
comment
@ n.m: вы неправы во всех отношениях, включая вашу глупую веру в то, что ошибочность аргументации авторитета, основанная на предполагаемой управленческой должности, произведет впечатление на кого угодно. это не так. это просто идиотизм.   -  person Cheers and hth. - Alf    schedule 27.01.2013
comment
@ Cheersandhth.-Альф: спасибо за вдумчивый и вежливый комментарий. Теперь, если вы решите привести несколько реальных аргументов в пользу своей позиции, это будет впечатляюще.   -  person n. 1.8e9-where's-my-share m.    schedule 27.01.2013
comment
@ Cheersandhth.-Alf: Уважаемый, я добавил информацию с помощью моих ответов ниже со ссылками, которые подтверждают, что мы не должны вызывать виртуальную функцию во время cons / dest, посмотрите. Просто комментарий вместо гиперссылки, почему бы не прочитать книги и не проверить компилятор? :) Если вы тратите время на чтение книг, вместо того, чтобы делать резервную копию чего-то, о чем вы не знаете, это будет полезно для вас и всех остальных :)   -  person Saqlain    schedule 27.01.2013
comment
@Saqlain: Хотя комментарии Альфа, как правило, носят подстрекательский (или высокомерный) характер, он действительно хорошо знает C ++ (как свидетельствует репутация), и в этом конкретном случае он прав. Стандарт C ++ полностью определяет, что произойдет, если вы вызовете виртуальную функцию из конструктора или деструктора; и поэтому это безопасно в этом отношении. Он также указывает, что вызов чистой виртуальной функции, кстати, является неопределенным поведением.   -  person Matthieu M.    schedule 27.01.2013
comment
@MatthieuM. Да, конечно, стандарт полностью определяет, что произойдет в этом случае. Это не оправдание для использования этой языковой конструкции в вашем реальном коде. Важно знать, что говорится в стандарте. Также важно знать, что будет делать с ним начинающий программист, которому поручено поддерживать ваш код через 5 лет, когда вас, автора, больше нет рядом.   -  person n. 1.8e9-where's-my-share m.    schedule 27.01.2013
comment
@ n.m .: А, но это совсем другое дело; и для протокола я согласен с тем, что нужно стремиться к тому, чтобы его кодовая база была достаточно простой, когда это возможно.   -  person Matthieu M.    schedule 27.01.2013


Ответы (3)


Вы не должны вызывать виртуальные функции во время строительства или разрушения, потому что вызовы не будут делать то, что вы думаете, и если бы они сделали, вы все равно были бы недовольны. Если вы восстанавливаетесь программистом на Java или C #, обратите пристальное внимание на этот элемент, потому что это место, где эти языки движутся, а C ++ - резкими движениями.

Переработайте свой дизайн, то есть вы можете вызвать некоторую функцию очистки до того, как объект будет уничтожен, идея состоит в том, чтобы просто избегать виртуальной функции во время const / dest (если они есть!), Если вы работаете с C ++ ...

Правила виртуального вызова разные. C ++ 2003, раздел 12.7 «Строительство и разрушение», говорит:

Давайте освежим старые воспоминания ...

Функции-члены, включая виртуальные функции (10.3), могут вызываться во время построения или разрушения (12.6.2). Когда виртуальная функция вызывается прямо или косвенно из конструктора (в том числе из инициализатора памяти для члена данных) или из деструктора, а объект, к которому применяется вызов, является строящимся или разрушаемым объектом, вызываемая функция является один, определенный в собственном классе конструктора или деструктора или в одной из его баз, но не функция, переопределяющая ее в классе, производном от класса конструктора или деструктора, или переопределяющая ее в одном из других базовых классов самого производного объекта (1.8 ). Если при вызове виртуальной функции используется явный доступ к члену класса (5.2.5), а объектное выражение относится к объекту, находящемуся в стадии создания или уничтожения, но его тип не является ни собственным классом конструктора, ни собственным классом деструктора, ни одной из его баз, результат вызов не определен.

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

Никогда не называйте виртуальные функции во время создания или разрушения отрывок из эффективного C ++, третье издание, Скотт Мейерс 6 июня 2005 г.

http://www.artima.com/cppsource/Nevercall.html

person Saqlain    schedule 27.01.2013
comment
-1 Вы не должны вызывать виртуальные функции во время строительства или разрушения, потому что вызовы не будут делать то, что вы думаете, и если бы они сделали, вы все равно были бы недовольны неверно. Кроме того, сравнение языков сильно вводит в заблуждение. Это троллинг. - person Cheers and hth. - Alf; 27.01.2013
comment
также вы можете вызвать некоторую функцию очистки до того, как объект будет уничтожен - это крайне плохой совет: многофазное разрушение так же плохо, как и многофазное строительство. - person Cheers and hth. - Alf; 27.01.2013
comment
Человек, который что-то знает о C ++, наверняка со мной согласится :-) То, что я сказал, также упоминается в Эффективном C ++, третьем издании Скотта Мейерса, лучше потратить некоторое время на чтение книг, а затем тратить время на комментирование over stackoverflow :-) - person Saqlain; 27.01.2013
comment
Вы пишете. Человек, который что-то знает о C ++, согласится ... Это означает, что я сказал, что вы ничего не знаете о C ++, что, хотя и не является откровенной ложью, очень сильно в этом направлении. Кроме того, это авторитетный аргумент, что является ошибкой, довольно глупым аргументом, поскольку первое заблуждение говорит читателю, что вы сами думаете, что ошибаетесь (независимо от того, правы вы или нет). Вместо того, чтобы диагностировать общую некомпетентность (как вы ошибочно подразумеваете), я написал, что этот ответ с тщательно отобранными неверными, но правдоподобными утверждениями является троллингом. - person Cheers and hth. - Alf; 27.01.2013
comment
Я снова предлагаю прочитать несколько книг вместо того, чтобы распространять неправильную информацию, я все еще поддерживаю мою точечную виртуальную функцию, которую никогда не следует вызывать во время const / dest, не переходите в другое место, где просто прочитайте Эффективный C ++, третье издание Скотта Мейерса :) Обсуждается точно такая же тема в этой книге также ... - person Saqlain; 27.01.2013
comment
в связанном с материалом материале Скотт пишет: «Это кажется разумным подходом к решению проблемы, касающимся вызова чистой виртуальной функции в деструкторе. Это не кажется разумным, за исключением совершенно неумелых. Т.е. Скотт здесь просто неправ; то, что он пишет, на самом деле не соответствует действительности. Скотт Мейерс внес большой вклад в C ++, включая синглтон Мейерса и оригинальные комментарии, в которых удалено автоматическое создание конструкторов перемещения и операторов присваивания. Но он также был в корне неправ в некоторых вещах, и вот он. Так что забудьте об этой пьесе как об авторитете. Это неверно. - person Cheers and hth. - Alf; 27.01.2013
comment
Но, дорогая, я точно сталкивался с той же проблемой в прошлом, и он решает эту проблему с поведением вызова во время выполнения :) В любом случае, хороших выходных. - person Saqlain; 27.01.2013
comment
вы можете подумать об этом так: в C ++ вы, как правило, не платите за то, что вам не нужно, но для построения и разрушения выполняется много работы по настройке динамического типа объекта в соответствии с тем, как построен или разрушен в любое время. и это только для того, чтобы виртуальные вызовы работали, как задумано. в java и C # это не делается, поэтому на этих языках вы рискуете получить доступ к неинициализированным или уже уничтоженным частям. но в C ++ это безопасно, и это оплачивается небольшой эффективностью. который никогда бы не вошел в дизайн языка, если бы вы не использовали его. - person Cheers and hth. - Alf; 27.01.2013
comment
хм, у меня есть стандарт CPP перед собой e-maxx.ru/bookz/files/cpp_standard .pdf, прочтите раздел, в котором говорится, что здесь нельзя вставить ... относится к объекту, находящемуся в стадии строительства или уничтожения, но его тип не является ни конструктором, ни собственным классом деструктора, ни одной из его баз, результат вызова не определен ..., это противоречит тому, что вы говорите ... и объявляет это как неопределенное или запрещенное :) - person Saqlain; 27.01.2013
comment
@Saqlain: он не определен только для чистых виртуальных функций. В противном случае он определяется, и динамический тип объекта считается текущим (статическим) типом. Книги Мейерса в основном нацелены на новичков, чтобы научить их упрощенным правилам, позволяющим избегать опасных областей C ++, они являются правилом большого пальца; и это очень хорошо; но вам необходимо тщательно оценить, приносят ли они пользу после того, как вы достаточно освоитесь с языком. - person Matthieu M.; 27.01.2013

Что касается стандарта C ++:

  • если вы вызываете виртуальную функцию в конструкторе или деструкторе, функция отправляется динамически, как если бы ее динамический тип был типом текущего выполняемого конструктора / деструктора (§12.7 / 4)
  • если эта функция произошла с чистой виртуальной машиной, то это неопределенное поведение (§10.4 / 6); Itanium ABI определяет поведение: __cxa_pure_virtual называется.

Итак, у вас есть немного острый вопрос ...


Возможное решение проблемы - разбить его на две части, чтобы разбить разрушение на две части. Этого можно добиться с помощью шаблона Strategy:

  • предоставить настраиваемый интерфейс, вашу стратегию
  • предоставить класс менеджера, который инкапсулирует функциональность и полагается на стратегию для настраиваемых частей

Сделаем понятнее:

class Interface {
public:
    friend class Manager;

private:
    virtual void finalize() = 0;
}; // class Interface


class Manager {
public:
    explicit Manager(std::unique_ptr<Interface>&&);

    ~Manager();

private:
    std::unique_ptr<Interface> _interface;
}; // class Manager

Manager::~Manager() {
    _interface->finalize();
}

Хитрость ? В точке, где вызывается finalize(), разрушение _interface еще не началось! Вызов деструктора произойдет позже; и таким образом вы не страдаете судьбой полумертвого объекта.

Я закончу этот ответ предупреждением о join-включении потока в деструктор. Помните, что деструкторы автоматически вызываются в случае раскрутки стека, поэтому может быть опасно ждать бесконечно во время сбоя; особенно, если поток ожидает данных, которые должны быть предоставлены текущим разворачиваемым потоком ... классический случай мертвой блокировки.


Ссылки (n3337):

§12.7 / 4 Функции-члены, включая виртуальные функции (10.3), могут вызываться во время построения или разрушения (12.6.2). Когда виртуальная функция вызывается прямо или косвенно из конструктора или деструктора, в том числе во время создания или уничтожения нестатических элементов данных класса, и объект, к которому применяется вызов, является строящимся объектом (назовите его x) или уничтожение, вызываемая функция является последним переопределителем в классе конструктора или деструктора, а не переопределяющим ее в более производном классе.

§10.4 / 6 Функции-члены могут быть вызваны из конструктора (или деструктора) абстрактного класса; эффект выполнения виртуального вызова (10.3) чистой виртуальной функции прямо или косвенно для объекта, создаваемого (или уничтожаемого) из такого конструктора (или деструктора), не определен.

person Matthieu M.    schedule 27.01.2013
comment
-1, если вы вызываете виртуальную функцию в конструкторе или деструкторе, функция не отправляется динамически; вызываемая функция текущего класса неверна. это не будет реализация в производном классе, но это не то, что утверждает цитируемый текст. кричите, если хотите примера, укажите главу и стих, которые сбили вас с толку, если хотите обсуждения. - person Cheers and hth. - Alf; 28.01.2013
comment
@ Cheersandhth.-Alf: Я сам написал главы и стихи, вас беспокоит формулировка? (Я открыт для предложений). - person Matthieu M.; 28.01.2013
comment
final overrider не означает то, как вы его парафразируете. Вызов функций отправляется динамически. То, что вы пишете, - это не то, что говорится в стандарте, то, что вы пишете, не означает то же самое, что то, что вы цитируете из стандарта, и то, что вы пишете, может привести к тому, что большая часть кода C ++ перестанет работать. Это не имеет ничего общего с простой формулировкой. Это совершенно неправильно. - person Cheers and hth. - Alf; 28.01.2013
comment
@ Cheersandhth.-Альф: Я понимаю, что вы имеете в виду, я переформулировал объяснение, чтобы оно было ближе к Стандарту и относилось к случаю, когда функция virtual вызывается через ссылку на базовый класс. Я все еще не совсем удовлетворен им (я думаю, слишком тупой), и поэтому все еще открыт для предложений. - person Matthieu M.; 28.01.2013
comment
Думаю, сейчас все в порядке. Согласно другой формулировке, во время создания и уничтожения класса T динамическим типом является T, поэтому вызовы ведут себя так же, как если бы объект был создан как T (что вполне могло бы быть, в общем, ). В любом случае, сейчас все в порядке, но пример завершения в ответе излишне хрупкий. Вместо шаблонов Managers, Singletons, BlahBlah и т. Д., Вдохновленных Java, просто введите класс, производный от рассматриваемого класса, и выполните действия по уничтожению в деструкторе. Вот для чего это нужно. - person Cheers and hth. - Alf; 28.01.2013

Ты пишешь,

Я пытаюсь вызвать чистые виртуальные функции из деструктора моего базового класса, показанного ниже.

И рассматриваемый код

TailFileManager::~TailFileManager()
{
    m_Stop = true;
    m_WorkerThread->join();
}

К счастью, в однопоточном исполнении это не могло вызвать чисто виртуальную функцию. Но поток, который вы join обрабатываете, может вызвать чистую виртуальную функцию для этого объекта, возможно, через невиртуальную функцию-член. Если это так, то проблема связана с потоковой передачей, в частности, с управлением временем жизни этого объекта.

К сожалению, вы не показываете соответствующий код. Попробуйте свести все к небольшому, законченному, работающему примеру. Где «работает» в том смысле, что воспроизводит проблему.

person Cheers and hth. - Alf    schedule 27.01.2013