2015-07-30 3 views
0

Аки: Есть ли «Вызов виртуалов Во время деинициализации» идиомЕсть ли динамическое связывание во время деинициализации идиомы

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

Исправление для конструкторов было простым. Я переместил виртуальные вызовы на статический шаблон Create и сделал все защищенные конструкторы. Тогда все, что мне нужно было сделать, - это скомпилировать и изменить все ошибки, вызывающие ошибки, для использования шаблона Create. Минимальный шанс для регрессий. Однако для деструкторов аналога этому нет.

Как бы вы это разрешили?

Пример кода

#include <iostream> 

class Base 
{ 
public: 
    virtual ~Base() 
    { 
     DeInit(); 
    } 
protected: 
    virtual void DeInit() 
    { 
     std::cout << "Base" << std::endl; 
    } 
}; 

class Derived : public Base 
{ 
protected: 
    virtual void DeInit() override 
    { 
     std::cout << "Derived" << std::endl; 
     Base::DeInit(); 
    } 
}; 

int main() 
{ 
    Derived d; 
} 

Этот код не вызывает Derived::DeInit (только отпечатки "Base"). Мне нужно решить такие проблемы.

Working example code

+2

Какую проблему вы должны решить? Виртуальные деструкторы уже работают по назначению. –

+0

@KerrekSB: OP хочет избежать виртуальных вызовов в конструкторе/деструкторе, поскольку поведение вызова виртуального метода там не такое же, как в другом контексте. – Jarod42

ответ

0

Решение, вдохновленное второй мыслью MSalters.

Это решение требует изменений только для класса Base и для создания классов Derived. Никаких изменений, необходимых для реализации Derived.

#include <iostream> 
#include <memory> 

class Base 
{ 
private: 
    template <class T> 
    class WithAutoDeInit : public T 
    { 
    public: 
     virtual ~WithAutoDeInit() override 
     { 
      T::DeInit(); 
     } 
    }; 

public: 
    template <class T> 
    static std::unique_ptr<typename std::enable_if<std::is_base_of<Base, T>::value, WithAutoDeInit<T>>::type> Create() 
    { 
     return std::make_unique<WithAutoDeInit<T>>(); 
    } 

    virtual ~Base() = default; 

protected: 
    virtual void DeInit() 
    { 
     std::cout << "Base" << std::endl; 
    } 
}; 

class Derived : public Base 
{ 
protected: 
    virtual void DeInit() override 
    { 
     std::cout << "Derived" << std::endl; 
     Base::DeInit(); 
    } 
}; 

int main() 
{ 
    Base::Create<Derived>(); 
} 

Working example code

Это не является надежным решением. Вы также можете делать экземпляры Derived напрямую. И если вы обновите все свои классы Derived с помощью защищенных конструкторов, разработчик, не знающий, может по-прежнему создавать новый класс, забывающий сделать его конструкторы защищенными.Интересно, может ли это быть усилено каким-то утверждением в стратегическом месте?

static_assert(std::is_constructible<Derived>::value, "Derived class is constructable");

Btw: Я, наконец, выбрать rewite код. Это управляемо, я думаю, и получившийся код будет более простым (следовательно, лучше).

1
... 
virtual Base::~Base() 
{ 
    Base::DeInit(); 
} 
... 

... 
Derived::~Derived() 
{ 
    // de-initialization code 
    // do not call Derived::DeInit() here as otherwise Base::DeInit() 
    // will be called two times 
} 
... 

и очистка виртуальной функции требует от деструкторов, когда кровянистые выделения их.

+1

Если вы это сделаете, почему бы не удалить «DeInit» вообще? Который часто является правильным выбором, BTW. – MSalters

+0

@Msalters - это сводится к тому, что автор может и не может коснуться и может ли он эффективно (= автоматически) «встроенный» метод вызовов, выполняемых от деструкторов – bobah

1

Это довольно сложно, так как деструкторы вызываются автоматически, покидающих областей, будь нормальным потоком, break, continue, return или throw. Вот почему вы не можете передавать аргументы деструктору.

Простое решение для звонка: Derived::DeInit от Derived::~Derived. Это имеет дополнительное преимущество, поскольку все еще есть Derived членов.

Другим является создание собственного класса интеллектуальных указателей, который вызывает T::DeInit до T::~T. Чтобы этого не было, верните этот умный указатель из своего Create.

0

Вам не нужно иметь виртуальное развлечение DeInit.

#include <iostream> 

    class Base 
    { 
    public: 
     virtual ~Base() 
     { 
      DeInit(); //this calls Base version 
     } 
    protected: 
     void DeInit() 
     { 
      std::cout << "Base" << std::endl; 
     } 
    }; 

    class Derived : public Base 
    { 

    public: 
     ~Derived() 
     { 
      DeInit(); //this calls Derived version 
     } 
    protected: 
     void DeInit() 
     { 
     std::cout << "Derived" << std::endl; 
     } 
    }; 

    int main() 
    { 
     Derived d; 
    } 

Выход: производный Base

это то, что вы хотели?

+0

Это хорошее решение, если вы можете (смеете) изменить Derived. Если это так, я бы даже убил методы DeInit (и 'Init') и пошел правильно RAII. Однако, если база кода огромна, а «Base» - общий базовый класс, то количество классов «Derived» может быть огромным. Возможно, сотни или более. Внедрение изменений для всех без надлежащего анализа и тестирования может быть плохой идеей. – Mathias

 Смежные вопросы

  • Нет связанных вопросов^_^