Как иметь дело с функцией-членом `init ()` внутри конструктора и с наследованием - PullRequest
0 голосов
/ 29 ноября 2018

Представьте себе эти классы:

class Base {
public:
    Base() : Base(false)
    { }
    virtual ~Base() = default;

    void init()
    {
        cout << "Base::init" << endl;
        check();
        // ...
    }

    virtual void check()
    {
        cout << "Base::check" << endl;
        // ...
    }
protected:
    Base(bool skip_init)
    {
        cout << "Base::Base" << endl;
        if (!skip_init) init();
    }
};

class Derived : public Base {
public:
    Derived() : Base(true)
    {
        cout << "Derived::Derived" << endl;
        init();
    }
    virtual ~Derived() = default;

    void init()
    {
        cout << "Derived::init" << endl;
        Base::init();
        // ...
    }

    virtual void check() override
    {
        cout << "Derived::check" << endl;
        Base::check();
        // ...
    }
};

Тогда создание экземпляра Derived приведет к

Base::Base
Derived::Derived
Derived::init
Base::init
Derived::check
Base::check

, чего я и хочу достичь.Он удовлетворяет этим требованиям:

  • Base определяет init() с операциями, общими для всех подклассов, и должен использоваться сразу после создания целого объекта (и только * там)
  • init() может содержать virtual функций внутри, но так как он должен вызываться только в конечном конструкторе, он не должен причинять никакого вреда
  • check() может бытьвызывается в любое время, не только с init() (он должен быть независим от него), и всегда должен выполнять все проверки, а не только те, которые относятся к подклассу

Мой лучший подход на данный момент, как указано выше, должен был использовать защищенный конструктор с флагом, который избегает вызова «неполного» * ​​1028 *, потому что виртуальные функции не работают в конструкторе суперкласса.(Без флага Base::check() будет вызван дважды.)

Мой вопрос: разве не существует лучшего, желательно каким-то стандартного метода, который имеет дело с вызовом виртуальных подпрограмм после целого объекта * 1033?* инициализирован (простите за расплывчатую терминологию)?И, конечно, не требуя от пользователей явного вызова init() (он должен оставаться защищенным).

Один из возможных вариантов использования (мой): Base обозначает, например, массив общих математических формул, которые должны удовлетворять нескольким ограничениям.,Derived (ia) ограничивает эти ограничения, добавляет некоторые, может отменять некоторые конкретные проверки, но в основном все еще использует их из Base.Например, Base::check_formulas() применяется check_formula(f) к каждому f, а Derived необходимо переопределить только check_formula функцию.

EDIT :

Как естьЛучше вообще избегать виртуальных функций внутри конструкторов, кажется, что невозможно вызвать вызов виртуальной функции изнутри самого объекта, поэтому перед вызовом этих функций объект должен быть "внешним".

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

Оба они вдохновили меня на это решение, которое похожедля @ Caleth'а, как мне показалось, более простым и понятным:

template <typename T, typename... Args>
T create(Args&&... args)
{
    T t(forward<Args>(args)...);
    t.init();
    return t;
}

class Base {
public:
    virtual ~Base() = default;
    Base(const Base& rhs) = default;
    Base(Base&& rhs) = default;
    Base& operator=(const Base& rhs) = default;
    Base& operator=(Base&& rhs) = default;

    template <typename T, typename... Args>
    friend T create(Args&&... args);
protected:
    Base() : _arg(0)
    {
        cout << "Base::Base()" << endl;
    }

    Base(int arg) : _arg(arg)
    {
        cout << "Base::Base(int)" << endl;
    }

    virtual void init()
    {
        cout << "Base::init" << endl;
        check();
        // ...
    }

    virtual void check()
    {
        cout << "Base::check" << endl;
        // ...
    }
private:
    int _arg;
};

class Derived : public Base {
public:
    virtual ~Derived() = default;

    template <typename T, typename... Args>
    friend T create(Args&&... args);
protected:
    Derived() : Base()
    {
        cout << "Derived::Derived()" << endl;
    }

    Derived(int arg) : Base(arg)
    {
        cout << "Derived::Derived(int)" << endl;
    }

    void init() override
    {
        cout << "Derived::init" << endl;
        Base::init();
        // ...
    }

    void check() override
    {
        cout << "Derived::check" << endl;
        Base::check();
        // ...
    }
};

Использование:

cout << endl << "Base() ..." << endl;
Base b1 = create<Base>();
cout << endl << "Base(int) ..." << endl;
Base b2 = create<Base>(5);
cout << endl << "Derived() ..." << endl;
Derived d1 = create<Derived>();
cout << endl << "Derived(int) ..." << endl;
Derived d2 = create<Derived>(10);

Вывод:

Base() ...
Base::Base()
Base::init
Base::check

Base(int) ...
Base::Base(int)
Base::init
Base::check

Derived() ...
Base::Base()
Derived::Derived()
Derived::init
Base::init
Derived::check
Base::check

Derived(int) ...
Base::Base(int)
Derived::Derived(int)
Derived::init
Base::init
Derived::check
Base::check

Любые другие предложения?

Ответы [ 3 ]

0 голосов
/ 29 ноября 2018

У меня нет общедоступного конструктора, но вместо этого есть (друг) make_base и make_derived фабричные функции, которые вызывают init для полностью построенного объекта.

class Base {
public:
    virtual ~Base() = default;
    Base(const Base &) = default;
    Base(Base &&) = default;
    Base& operator=(const Base &) = default;
    Base& operator=(Base &&) = default;

    friend Base make_base() { Base b; b.init(); return b; } 

protected:
    virtual void init()
    {
        cout << "Base::init" << endl;
        check();
        // ...
    }

    virtual void check()
    {
        cout << "Base::check" << endl;
        // ...
    }

    Base()
    {
        cout << "Base::Base" << endl;
    }
};


class Derived : public Base {
    friend Derived make_derived() { Derived b; b.init(); return b; }
protected:
    Derived() : Base()
    {
        cout << "Derived::Derived" << endl;
    }

    void init() override
    {
        Base::init();
        cout << "Derived::init" << endl;
        // ...
    }

    void check() override
    {
        Base::check();
        cout << "Derived::check" << endl;
        // ...
    }
};
0 голосов
/ 29 ноября 2018

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

class BaseHolder;
class Base {
private:
    void init() {
      // do more things
    }
    friend class BaseHolder;
protected:
    class BuildToken {
        explicit BuildToken() {}
        friend class BaseHolder;
    };
    Base(BuildToken) {
      // do your thing.
    }
};

template<typename>
struct MakeType{};

template<typename T>
inline constexpr MakeType<T> make{};

class BaseHolder {
    std::unique_ptr<Base> managed;
public:
    template<typename T>
    BaseHolder(MakeType<T>) : managed(new T(Base::BuildToken())) {
      managed->init();
    }
};

Теперь ни один производный класс не может сам вызывать init, и при этом он не может быть вызван где-либо, кроме как Base и BaseHolder.Все, что нужно сделать производному классу, это определить c'or, который принимает BuildToken и перенаправляет его в базу.Однако производные классы не могут по умолчанию инициализировать BuildToken сами объекты, они могут только скопировать их для пересылки на свою базу.Единственное место, где может быть создан токен, - BaseHolder.Это также единственное место, где init будет вызвано в нужное время.

Утилита MakeType предназначена для того, чтобы упростить для глаз объявление BaseHolder, чтобы он выглядел так:

BaseHolder item(make<Derived>);
0 голосов
/ 29 ноября 2018
  • init() может содержать виртуальных функций внутри , но поскольку он должен вызываться только в конечном конструкторе, он не должен причинять никакого вреда.

Даже если он вызывается через конечный конструктор , любая из таблиц для вызова этих виртуальных функций не инициализируется в этот момент.

Таким образом, вы не можете гарантировать правильностьпредполагаемое поведение.

Подробнее об этой проблеме см. здесь:

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...