Вызовите виртуальный метод сразу после создания - PullRequest
5 голосов
/ 20 октября 2010

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

Вот упрощенный пример:

struct Loader {
    int get(int index) { return 0; }
};

struct Base{
    Base() {
        Loader l; 
        load( l ); // <-- pure virtual call!
    }
    virtual void load( Loader & ) = 0;
};

struct Derived: public Base {
    int value;
    void load( Loader &l ) {
        value = Loader.get(0);
    }
};

Я могу вызвать load вконструктор Derived, но Derived не мог знать, как создать Loader.Любые идеи / обходные пути?

Ответы [ 8 ]

6 голосов
/ 20 октября 2010

Проблема в том, что построение базового класса происходит до того, как производный класс будет полностью создан. Вы должны либо вызвать «load» из производного класса, инициализировать через другую виртуальную функцию-член, либо создать вспомогательную функцию для этого:

Base* CreateDerived()
{
    Base* pRet = new Derived;
    pRet->Load();
    return pRet;
}
3 голосов
/ 21 октября 2010

Часто задаваемые вопросы по C ++ вызывают эту проблему DBDI , Динамическое связывание во время построения .Главным образом, проблема заключается в том, чтобы избежать двухфазного построения Зла, о котором говорится в других ответах.Это своего рода «мой» пункт часто задаваемых вопросов - я убедил Маршалла добавить его.

Тем не менее, Маршалл воспринимает его как весьма общий (что хорошо для FAQ), в то время как я больше интересовался конкретнымдизайн / шаблон кодирования.

Итак, вместо того, чтобы отправлять вас в FAQ, я отправляю вас в мой собственный блог, статью «Как избежать пост-постройки с использованием фабрики деталей» , котораяссылки на соответствующий пункт часто задаваемых вопросов, но подробно обсуждает шаблон.

Вы можете просто пропустить первые два абзаца ...

Я как-то там бродил.: -)

Приветствия и hth.,

2 голосов
/ 21 октября 2010

Используйте шаблон PIMPL:

template<typename T>
class Pimpl
{
    public:
        Pimpl()
        {
            // At this point the object you have created is fully constructed.
            // So now you can call the virtual method on it.
            object.load();
        }
        T* operator->()
        {
            // Use the pointer notation to get access to your object
            // and its members.
            return &object;
        }
    private:
        T    object;   // Not technically a pointer
                       // But otherwise the pattern is the same.
                       // Modify to your needs.
};

int main()
{
    Pimpl<Derived>   x;
    x->doStuff();
}
1 голос
/ 21 октября 2010

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

1 голос
/ 21 октября 2010

Не можете ли вы добавить метод getLoader() в свой класс Base, чтобы конструктор DerivedClass мог вызвать его на this, чтобы получить Loader?Поскольку DerivedClass конструктор будет вызываться после Base конструктора класса, он должен работать нормально.

0 голосов
/ 21 октября 2010

Это может произойти немного позже после других ответов, но я все равно попробую.

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

В принципе, вы можете применить любопытно повторяющийся шаблон шаблона и ввести код инициализации после вызова конструктора. Кроме того, если вы сделаете это так, как я написал ниже, вы даже сможете защитить load от повторного вызова.

struct Loader {
    int get(int index) { return 0; }
};
struct Base {
    virtual ~Base() {} // Note: don't forget this.
protected:
    virtual void load( Loader & ) = 0;
};

struct Derived : public Base {
    int value;
protected:
    void load( Loader &l ) {
        value = l.get(0);
    }
};

template<typename T>
class Loaded : public T
{
public:
    Loaded () {
        Loader l; T::load(l);
    }
};

int main ( int, char ** )
{
    Loaded<Derived> derived;
}

Честно говоря, я бы рассмотрел альтернативный дизайн, если вы можете. Переместите код из load в ваши конструкторы и предоставьте загрузчик в качестве ссылочного аргумента по умолчанию следующим образом:

struct Derived : public Base {
     Derived ( Loader& loader = Loader() ) { ... }
}; 

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

Сводка : ваш выбор следующий:

  1. Если вы не ограничены внешними ограничениями и не имеете обширной базы кода в зависимости от этого, измените свой дизайн на что-то более безопасное.
  2. Если вы хотите сохранить load таким, какой он есть, и не слишком менять свои классы, но готовы платить цену за изменение всех экземпляров, примените CRTP, как предложено выше.
  3. Если вы настаиваете на большей обратной совместимости с существующим клиентским кодом, вам придется изменить свои классы на использование PIMPL, как предлагали другие, или жить с существующей проблемой.
0 голосов
/ 21 октября 2010

Есть много способов исправить это, вот 1 предложение, подходящее в рамках вашей системы

struct Loader {
    int get(int index) { return 0; }
};

struct Base{
    Base() {
    }
    Loader & getLoader( );
private:
    Loader l; 
};

struct Derived: public Base {
    int value;
    Derived( ) {
        value = getLoader().get(0);
    }
};
0 голосов
/ 20 октября 2010

Многие известные фреймворки (например, MFC) делают это: они создают (виртуальную) функцию-член Init () или Create () и выполняют инициализацию, а затем указывают в документации, что пользователь вызывает ее.Я знаю, что вам не понравится эта идея, но вы просто не можете вызвать виртуальный метод из конструктора и ожидать, что он будет вести себя полиморфно, независимо от чистоты методов ...

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