Виртуальные деструкторы по умолчанию в C++

Цель состоит в том, чтобы отсеять неправильное использование ConfigureAwait в очень большой кодовой базе.

Некоторые команды выбирают для этого инструмент анализа кода. Есть несколько доступных. Наиболее распространенный подход, который я видел, состоит в том, чтобы потребовать a ConfigureAwait для каждого await и явно указать либо true, либо false. Это гарантирует, что каждый await был рассмотрен и поток контекста явно. Другие команды применяют специфичные для проекта правила «всегда использовать ConfigureAwait(false)» и просто зависят от проверки кода для проектов, которые не могут следовать этому правилу.

Проблема с вашим примером кода состоит в том, что невозможно для DoWhatever узнать, был ли он вызван косвенно, из-за Task.Run. Если вы переписываете эти методы, это становится ясным:

public static async Task CapturesContext()
{
  var task = Task.Run(() => DoWhatever());
  await task;
}

public static async Task DoesNotCaptureContext()
{
  var task = Task.Run(() => DoWhatever());
  var configuredTask = task.ConfigureAwait(false);
  await configuredTask;
}

Первые строки переписанных методов должны прояснить, что DoWhatever не имеет представления, захватят ли CapturesContext или DoesNotCaptureContext контекст или не. Обратите внимание на «волю» (будущее время) - вполне возможно, что DoWhatever выполняется и заканчивает выполнение до того, как ConfigureAwait(false) даже будет вызван.

Теперь вы можете проверить изнутри задачи, выполняется ли она в контексте прямо сейчас . Но в этом случае для обоих примеров методов DoWhatever не увидят контекст из-за Task.Run. Так что это не поможет вам обнаружить тот факт, что CapturesContext действительно захватывает контекст; DoWhatever не видит контекст, поэтому не может его обнаружить.

Пользовательский SynchronizationContext является хорошим решением для модульных тестов, но его было бы неудобно использовать во время выполнения, так как у вас есть некоторые методы, которым нужен контекст. По этой причине большинство команд предпочитают полагаться на проверку кода и / или инструменты анализа кода.

16
задан j0k 1 July 2013 в 14:09
поделиться

4 ответа

Да - базовому классу нужен виртуальный деструктор, даже если он пуст. Если этого не сделать, то, когда что-то удалит производный объект через базовый указатель / ссылку, объекты-члены производного объекта не получат шанса уничтожить себя должным образом.

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

42
ответ дан 30 November 2019 в 15:04
поделиться

Вам не нужно делать деструктор абстрактным, просто дайте ему пустую реализацию:

virtual ~criterion() { }

Таким образом, вы не обязаны реализовывать его в каждом дочернем классе, но все равно в каждом из них. будет иметь (унаследованный) виртуальный деструктор.

12
ответ дан 30 November 2019 в 15:04
поделиться

Рекомендуется вставить

virtual ~criterion() {}

, чтобы избежать удаления из проблемы с указателем базового класса. В противном случае вы потеряете память, так как не будут вызываться деструкторы производных классов.

criterion *c = new fastFilter();
delete c; // leaks
25
ответ дан 30 November 2019 в 15:04
поделиться

One small change from what others have already answered:

Instead of

virtual void ~criterion() = 0;

the required version is:

    virtual ~criterion() {}  //Note: Removed void as destructors not allowed 
                             //  a return type

To know more about virtual destructor have a look at this link from FAQ When should my destructor be virtual?

7
ответ дан 30 November 2019 в 15:04
поделиться
Другие вопросы по тегам:

Похожие вопросы: