Вызвать конструктор базового класса позже (не в списке инициализаторов) в C++

Я наследую класс и хочу вызвать один из его конструкторов. Однако мне нужно обработать некоторые вещи (которые не требуют ничего от базового класса) перед его вызовом. Есть ли способ, которым я могу просто вызвать его позже, вместо того, чтобы вызывать его в списке инициализаторов? Я считаю, что это можно сделать на Java и C#, но я не уверен в C++.

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


person placeholder    schedule 29.09.2010    source источник


Ответы (6)


Есть ли способ, которым я могу просто вызвать его позже, вместо того, чтобы вызывать его в списке инициализаторов?

Нет, ты не можешь. Конструктор базового класса должен быть вызван в списке инициализаторов, и он должен быть вызван первым.

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

Я считаю, что это можно сделать на Java и C#, но я не уверен в C++.

Ни C#, ни Java этого не допускают.

Однако вы можете вызвать метод в качестве аргумента вызова конструктора базового класса. Затем это обрабатывается перед конструктором:

class Derived {
public:
    Derived() : Base(some_function()) { }

private:
    static int some_function() { return 42; }
};
person Konrad Rudolph    schedule 29.09.2010
comment
Спасибо, не подумал просто вызвать функцию, так что это сработает, но будет немного запутанно, потому что конструктор имеет 2 параметра. Я приму этот ответ, когда смогу (если, конечно, не выйдет лучший). Кстати, вы правы в том, что это невозможно сделать в Java и C#, я думал, что это возможно в Java, потому что это делается с использованием super(...) в теле метода, но теперь я заметил, что это должна быть первая строка. - person placeholder; 29.09.2010
comment
+1 Вы должны сделать some_function() статическим, чтобы задокументировать тот факт, что он не использует ни одну из переменных экземпляра класса (которые не были инициализированы). - person Martin York; 29.09.2010
comment
Интересно, никогда не видел, чтобы это было сделано раньше. Я предполагаю, что вызываемая функция может быть функцией производного класса. - person PatrickV; 29.09.2010
comment
@Patrick: его можно получить, но он не должен быть виртуальным и не должен обращаться к переменным-членам или указателю this. - person Konrad Rudolph; 29.09.2010
comment
@Konrad Rudolph: Является ли ограничение на переменные-члены жестким ограничением или это просто очень плохая идея? - person PatrickV; 29.09.2010
comment
@Patrick: компилятор не обязательно предупредит вас, но это действительно жесткое ограничение, поскольку объект еще не существует, как и его члены. До вызова конструктора их значения были просто мусором в памяти, и, кроме того, вы даже не можете получить к ним реальный доступ, поскольку указатель this еще не существует, а он необходим для вычисления местоположения переменных-членов. - person Konrad Rudolph; 29.09.2010
comment
Изменилась ли первая часть этого ответа с помощью С++ 11? Или, возможно, я неправильно понял вопрос/ответ - я прочитал его как говорящий, что конструктор Base должен быть первым вызовом в списке инициализаторов, но я смог скомпилировать иначе: Derived() : deriveVar(10), Base() {}. Вы хотели сказать, что этого нельзя допускать? - person dwanderson; 02.02.2016
comment
@dwanderson Это не изменилось. C++ не проверяет, используете ли вы правильный порядок в списке инициализаторов (хотя компилятор может выдать предупреждение), но порядок элементов в списке инициализаторов игнорируется: инициализация всегда происходит в тот же предопределенный порядок: сначала базовые классы, затем переменные-члены в порядке объявления переменных. - person Konrad Rudolph; 02.02.2016
comment
@KonradRudolph ах, вот что объяснял мой коллега, и я не был уверен, что это противоречит вашему ответу; Я вижу разницу сейчас. Спасибо за ответ - и за тему, которая была закрыта более 5 лет :) - person dwanderson; 02.02.2016

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

Учитывая фиксированный порядок инициализации, если у вас есть контроль над производным классом (а как иначе вы могли бы возиться с одним из его кторов?), вы можете проникнуть в частную базу, чтобы она была инициализирована раньше других. base, который затем можно инициализировать с уже рассчитанными значениями частной базы:

class my_dirty_little_secret {
  // friend class the_class;
public: 
  my_dirty_little_secret(const std::string& str)
  {
    // however that calculates x, y, and z from str I wouldn't know
  }
  int x;
  std::string y;
  float z;
};

class the_class : private my_dirty_little_secret // must be first, see ctor
                , public the_other_base_class {
  public:
    the_class(const std::string str)
      : my_dirty_little_secret(str)
      , the_other_base_class(x, y, z)
    {
    }
  // ...
};

Класс my_dirty_little_secret является частной базой, поэтому пользователи the_class не могут его использовать, все его материалы также являются частными, с явной дружбой, предоставляющей доступ к нему только the_class. Однако, поскольку он указан первым в списке базовых классов, он будет надежно сконструирован до the_other_base_class, поэтому все, что он вычислит, может быть использовано для его инициализации.
Хороший комментарий в списке базовых классов, надеюсь, не даст другим нарушить работу путем рефакторинга. .

person sbi    schedule 29.09.2010

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

person Alok Save    schedule 29.09.2010

Вау, мы все когда-то были молодыми. Этот ответ не сработает, поэтому не используйте его. Контент оставлен для исторических целей.

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

class Base
{
public:
    Base()
    {
        Initialize();
    }
protected:
    virtual void Initialize()
    {
        //do initialization;
    }
};

class Derived : Base
{
public:

    Derived() : Base()
    {
    }
protected:
    virtual void Initialize()
    {
        //Do my initialization
        //call base
        Base::Initialize();
    }
};
person PatrickV    schedule 29.09.2010
comment
К сожалению, у меня нет контроля над базовым классом. - person placeholder; 29.09.2010
comment
Возможно, тогда вы не сможете достичь своей цели. Если вы опубликуете больше кода, возможно, мы сможем что-то придумать, но я никогда не видел, чтобы это делалось на C++. - person PatrickV; 29.09.2010
comment
Мне не нравится эта идея. Двухэтапное строительство всегда подвержено ошибкам. - person sbi; 29.09.2010
comment
@sbi: Не стесняйтесь, вам это не нравится ... но если вам нужно получить доступ к данным члена в С++ до инициализации базового класса, это практически единственный вариант. По крайней мере, единственный, о котором я знаю. Я бы хотел, чтобы было более чистое решение, но это самое чистое решение, о котором я знаю. Задающему вопрос может не понадобиться доступ к данным члена, но другой читатель, который столкнется с этим, может, и это намного чище, чем добавление статической функции в класс IMO. - person PatrickV; 29.09.2010
comment
@PatrickV: я опубликовал рабочее решение, которое не требует двухэтапного построения. Правда, иногда двухэтапное строительство кажется проще. Но если бы я когда-нибудь использовал его, я бы, конечно, спрятал его за объектом с простым конструктором. Ну, если подумать, в основном это то, что делает мое решение... - person sbi; 29.09.2010
comment
@sbi: Это аккуратно, но мне не нравится такой подход. Я не подумал об этом и рад, что вы опубликовали это, поэтому я дал вам +1, но использовать множественное наследование для решения этой проблемы? Когда вы наследуете что-то, вы должны иметь возможность встать перед президентом вашей компании и громко заявить, что класс A — это класс B. В этом случае вы бы сказали, что the_class — это прежде всего my_dirty_little_secret, а также the_other_base_class. Просто чтобы обмануть строительство? Вероятно, есть место для обоих решений. Я считаю, что только один из них переносим на С#. Хотя в этом не уверен, все еще учусь. - person PatrickV; 30.09.2010
comment
@PatrickV: Вы путаете публичное и частное наследование. Публичное наследование описывает отношение Is-A, а частное — нет. Я предлагаю вам ознакомиться с этой темой. - person sbi; 30.09.2010
comment
@sbi Я полностью прочитал эту тему. Я предлагаю вам вернуться и прочитать мой последний пост немного внимательнее. - person PatrickV; 30.09.2010
comment
@PatrickV: Извините, но для меня это расплывчато. Вы написали, что the_class — это прежде всего my_dirty_little_secret. Это просто неправильно. Я имел в виду это в своем объяснении. Что вы имеете в виду? - person sbi; 30.09.2010
comment
@sbi: Извините, был в отпуске. Я предполагаю, что я имел в виду, что ваш подход усложняет дизайн программного обеспечения с точки зрения наследования по сравнению с моим. Оба имеют свои достоинства. Это просто зависит от вашей ситуации. Я знаю, что выполнение того, что вы предложили, должно было бы соответствовать моим диаграммам наследования классов, и на этом уровне мы хотим, чтобы эти диаграммы были как можно более простыми. Я бы предпочел то, что вы описываете как подверженную ошибкам двухфазную конструкцию, если это упрощает конструкцию. И опять же, я не думаю, что этот подход переносим на C#. Но я хотел бы ошибаться в этом. - person PatrickV; 07.10.2010
comment
@PatrickV: Тому, кто заставляет вас помещать частное наследование в схему вывода, следует предложить прочитать вводную книгу по C++ или вытащить из комнаты и молча расстрелять. Частное наследование такое же, как и частные данные: это никого не касается, кроме разработчиков и друзей класса. - person sbi; 07.10.2010
comment
@sbi: это досадное ограничение инструмента. Чудеса работы с новейшими технологиями, которые мог предложить 1997 год. Ваши налоговые доллары на работе! (ну, по крайней мере, некоторые из вас) Это проблема реального мира, с которой некоторые из нас имеют дело - инструмент проектирования заставляет это на диаграмме, что заставляет это в отчете, который автоматически помещается в слайд-шоу, который предшествует главный системный архитектор, который может понимать или не понимать частное наследование. В моем случае двухступенчатая конструкция является более разумным вариантом. Чем больше инструментов у вас есть в наборе инструментов, тем лучше для вас. - person PatrickV; 08.10.2010
comment
@PatrickV: Хотя надоедливая офисная политика и произвольные ограничения некоторых инструментов, которые вы должны использовать в результате этого, могут облегчить вам написание сложного в обслуживании кода, это не означает, что это лучше сделать так вообще. - person sbi; 08.10.2010
comment
О, ладно, пытался отредактировать это, но это не позволяет мне. Вероятно, это прозвучит как колкий ответ — одно из наших высказываний, которые мы выбрасываем по 50 раз в день в офисе. Если обидно, сби, извиняюсь. Моя точка зрения заключалась в том, что я слышал вашу точку зрения на дебаты и не согласен. Повторное повторение одного и того же пункта не заставит меня согласиться. Ничего страшного, что мы не согласны — теперь давайте вернемся к помощи нуждающимся. - person PatrickV; 09.10.2010
comment
С этим подходом есть большая проблема. В C++ вы не можете использовать виртуальные функции в конструкторах. В конструкторе базового класса объект еще не «созрел» до типа своего производного класса. Если вы вызовете виртуальную функцию оттуда, она вызовет версию этой функции базового класса. - person Chris; 03.10.2013

Другой вариант, основанный на предложении @Konrad, состоит в том, чтобы иметь статический метод для создания объекта, например:

class Derived {
public:
    Derived(int p1, int p2, int p3) : Base(p1, p2) { }

    static Derived* CreateDerived(int p3) { return new Derived(42, 314, p3); }
};

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

person Nande    schedule 24.12.2020

struct base{
   base(int x){}
};

struct derived : base{
   derived(int x) : base(x){}
};

Именно так конструкторы базового класса вызываются в C++ из списка инициализации производного класса.

person Chubsdad    schedule 29.09.2010
comment
Это не то, о чем он спрашивает. - person Prasoon Saurav; 29.09.2010