Каковы правила вызова конструктора суперкласса?

Каковы правила C++ для вызова конструктора суперкласса из подкласса?

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

Просто придирки: в C++ нет "суперкласса", фактически, стандарт вообще не упоминает об этом. Эта формулировка происходит из Java (скорее всего). Используйте «базовый класс» в C++. Я предполагаю, что супер подразумевает единственного родителя, а C++ допускает множественное наследование.

andreee 11.06.2018 15:44

@andreee Я сообщаю, что super class также называется base class, например, parent class. в наборе инструментов qt sub class - в этом порядке child class также называется super class. Возможно, это помогает избежать некоторой потенциальной путаницы в терминологии.

Ivanovic 15.06.2020 17:31
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
736
2
846 735
10
Перейти к ответу Данный вопрос помечен как решенный

Ответы 10

Ответ принят как подходящий

Конструкторы базового класса вызываются автоматически, если у них нет аргументов. Если вы хотите вызвать конструктор суперкласса с аргументом, вы должны использовать список инициализации конструктора подкласса. В отличие от Java, C++ поддерживает множественное наследование (к лучшему или худшему), поэтому к базовому классу следует обращаться по имени, а не по «super ()».

class SuperClass
{
    public:

        SuperClass(int foo)
        {
            // do something with foo
        }
};

class SubClass : public SuperClass
{
    public:

        SubClass(int foo, int bar)
        : SuperClass(foo)    // Call the superclass constructor in the subclass' initialization list.
        {
            // do something with bar
        }
};

Подробнее о списке инициализации конструктора здесь и здесь.

Я удалил «явное» из конструктора SuperClass. Несмотря на то, что это лучшая практика для конструкторов с одним аргументом, она не имела отношения к обсуждаемому вопросу. Для получения дополнительной информации о явном ключевом слове см .: weblogs.asp.net/kennykerr/archive/2004/08/31/…

luke 24.09.2008 16:38

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

ha9u63ar 31.10.2014 12:33

@hagubear, действительно только для конструкторов, AFAIK

luke 31.10.2014 15:24

Когда вы создаете экземпляр объекта SubClass, скажем, SubClass anObject(1,2), передается ли 1 в SuperClass(foo) (становится аргументом параметра foo)? Я просматривал документы сверху и снизу, но ни один из них окончательно не заявил, что аргументы конструктора SubClass могут быть переданы в качестве аргументов конструктору SuperClass.

LazerSharks 24.11.2014 04:49

@Gnuey, обратите внимание на часть : SuperClass(foo). foo явно передается конструктору суперкласса.

luke 24.11.2014 05:27

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

LazerSharks 24.11.2014 07:01

Должна ли быть вызвана часть конструктора суперкласса из файла initializatoin (файл cpp) или достаточно объявить его в файле .h?

andig 04.02.2016 22:41

@andig: вызов находится там, где конструктор / определен /

luke 04.02.2016 22:56

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

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

class base
{
  public:
  base (int arg)
  {
  }
};

class derived : public base
{
  public:
  derived () : base (number)
  {
  }
};

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

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

Class2::Class2(string id) : Class1(id) {
....
}

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

В C++ конструкторы без аргументов для всех суперклассов и переменных-членов вызываются для вас перед вводом вашего конструктора. Если вы хотите передать им аргументы, существует отдельный синтаксис для этого, называемый «цепочка конструкторов», который выглядит следующим образом:

class Sub : public Base
{
  Sub(int x, int y)
  : Base(x), member(y)
  {
  }
  Type member;
};

Если что-либо, запущенное в этот момент, вызывает выбросы, у баз / членов, которые ранее завершили построение, вызываются их деструкторы, и исключение повторно передается вызывающему. Если вы хотите перехватывать исключения во время цепочки, вы должны использовать блок try функции:

class Sub : public Base
{
  Sub(int x, int y)
  try : Base(x), member(y)
  {
    // function body goes here
  } catch(const ExceptionType &e) {
    throw kaboom();
  }
  Type member;
};

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

Я не уверен, что понимаю синтаксис вашего второго примера ... Является ли конструкция try / catch заменой тела конструктора?

levik 23.09.2008 17:47

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

puetzk 23.09.2008 19:55

Благодарим за включение синтаксиса try / catch для инициализаторов. Я использую C++ 10 лет и впервые вижу такое.

jakar 08.06.2012 21:23

Я должен признать, что использую C++ долгое время, и это первый раз, когда я вижу, что try / catcn в списке конструкторов.

Cameron 21.11.2013 05:29

Я мог бы сказать, что тело функции «входит» в блок try - таким образом, любое тело, следующее за инициализаторами, также будет иметь свои исключения.

peterk 16.01.2019 18:28

CDerived::CDerived()
: CBase(...), iCount(0)  //this is the initialisation list. You can initialise member variables here too. (e.g. iCount := 0)
    {
    //construct body
    }

В C++ существует концепция списка инициализации конструктора, в котором вы можете и должны вызывать конструктор базового класса, а также инициализировать элементы данных. Список инициализации идет после подписи конструктора после двоеточия и перед телом конструктора. Допустим, у нас есть класс A:


class A : public B
{
public:
  A(int a, int b, int c);
private:
  int b_, c_;
};

Затем, если у B есть конструктор, который принимает int, конструктор A может выглядеть так:


A::A(int a, int b, int c) 
  : B(a), b_(b), c_(c) // initialization list
{
  // do something
}

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

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

Подождите ... Вы говорите, что инициализаторы экономят на назначениях. Но разве в них не выполняются те же задания, если они вызваны?

levik 23.09.2008 17:39

Неа. Инициализация и назначение - разные вещи. Когда вызывается конструктор, он пытается инициализировать каждый член данных тем, что, по его мнению, является значением по умолчанию. В списке инициализации вы можете указать значения по умолчанию. Таким образом, в любом случае вы несете затраты на инициализацию.

Dima 23.09.2008 17:44

И если вы используете присваивание внутри тела, вы все равно несете стоимость инициализации, а затем стоимость присваивания сверх этого.

Dima 23.09.2008 17:44

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

Benjamin 11.11.2014 20:17

Все упоминали вызов конструктора через список инициализации, но никто не сказал, что конструктор родительского класса может быть вызван явно из тела конструктора производного члена. См., Например, вопрос Вызов конструктора базового класса из тела конструктора подкласса. Дело в том, что если вы используете явный вызов родительского класса или конструктора суперкласса в теле производного класса, на самом деле это просто создает экземпляр родительского класса, а не вызывает конструктор родительского класса для производного объекта. . Единственный способ вызвать конструктор родительского класса или суперкласса для объекта производного класса - через список инициализации, а не в теле конструктора производного класса. Так что, возможно, это не следует называть «вызовом конструктора суперкласса». Я поместил этот ответ сюда, потому что кто-то может запутаться (как и я).

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

Richard Chambers 19.04.2014 17:22

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

TT_ 20.04.2014 03:56

«конструктор родительского класса может быть вызван явно из тела конструктора производного члена», что явно неверно для рассматриваемого экземпляра, если вы не имеете в виду размещение new, и даже в этом случае это неверно, потому что вам нужно сначала разрушить экземпляр. Например. MyClass::MyClass() { new (this) BaseClass; /* UB, totally wrong */ } - это синтаксис C++ для явный вызов конструктора. Так выглядит «вызов конструктора». Так что тот факт, что за этот абсурдно неправильный ответ получил поддержку, для меня является полной загадкой.

Kuba hasn't forgotten Monica 25.11.2020 19:27

Я считаю, что большинство ответов на этот вопрос, на который вы ссылаетесь, являются нежелательными или уходящими в сторону. Я написал ответ, которого все время не хватало, кажется. Я не удивлен, что это кто-то может запутаться, пытаясь понять что-нибудь из вашей ссылки ... Я тоже был сбит с толку. Это легкая вещь, но люди пишут об этом, как будто это волшебство. Слепой ведет слепого. Явный "вызов" конструктора осуществляется с размещением нового синтаксиса!MyClass() - это не какой-то "звонок"! Он имеет то же значение, что и, например, int(), и это создает ценность!

Kuba hasn't forgotten Monica 25.11.2020 20:03

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

Если об этом никто не говорил, где это было упомянуто?

user207421 12.02.2015 12:36

@EJP так как речь идет о правилах вызова, в ответе стоит упомянуть последовательность вызовов

Krishna Oza 12.02.2015 13:41

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

using namespace std;

class Base
{
    public:
    Base(int a=1) : _a(a) {}

    protected:
    int _a;
};

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

  void printit() { cout << _a << endl; }
};

int main()
{
   Derived d;
   d.printit();
   return 0;
}

Выход: 1

Это просто потому, что это конкретное объявление создает неявный Base(), который имеет то же тело, что и Base(int), но плюс неявный инициализатор для : _a{1}. Это Base(), который всегда вызывается, если в списке инициализации не указан конкретный базовый конструктор. И, как упоминалось в другом месте, конструкторы делегирования C++ 11 и инициализация равных скобок делают аргументы по умолчанию менее необходимыми (когда они уже были похожи на запах кода во многих примерах).

underscore_d 11.02.2016 02:21

Если вы просто хотите передать все аргументы конструктора базовому классу (= parent), вот минимальный пример.

При этом используются шаблоны для перенаправления каждого вызова конструктора с 1, 2 или 3 аргументами в родительский класс std::string.

Код

Live-версия

#include <iostream>
#include <string>

class ChildString: public std::string
{
    public:
        template<typename... Args>
        ChildString(Args... args): std::string(args...)
        {
            std::cout 
                << "\tConstructor call ChildString(nArgs = "
                << sizeof...(Args) << "): " << *this
                << std::endl;
        }

};

int main()
{
    std::cout << "Check out:" << std::endl;
    std::cout << "\thttp://www.cplusplus.com/reference/string/string/string/" << std::endl;
    std::cout << "for available string constructors" << std::endl;

    std::cout << std::endl;
    std::cout << "Initialization:" << std::endl;
    ChildString cs1 ("copy (2)");

    char char_arr[] = "from c-string (4)";
    ChildString cs2 (char_arr);

    std::string str = "substring (3)";
    ChildString cs3 (str, 0, str.length());

    std::cout << std::endl;
    std::cout << "Usage:" << std::endl;
    std::cout << "\tcs1: " << cs1 << std::endl;
    std::cout << "\tcs2: " << cs2 << std::endl;
    std::cout << "\tcs3: " << cs3 << std::endl;

    return 0;
}

Выход

Check out:
    http://www.cplusplus.com/reference/string/string/string/
for available string constructors

Initialization:
    Constructor call ChildString(nArgs=1): copy (2)
    Constructor call ChildString(nArgs=1): from c-string (4)
    Constructor call ChildString(nArgs=3): substring (3)

Usage:
    cs1: copy (2)
    cs2: from c-string (4)
    cs3: substring (3)

Обновление: использование вариативных шаблонов

Обобщить до n аргументов и упростить

        template <class C>
        ChildString(C arg): std::string(arg)
        {
            std::cout << "\tConstructor call ChildString(C arg): " << *this << std::endl;
        }
        template <class C1, class C2>
        ChildString(C1 arg1, C2 arg2): std::string(arg1, arg2)
        {
            std::cout << "\tConstructor call ChildString(C1 arg1, C2 arg2, C3 arg3): " << *this << std::endl;
        }
        template <class C1, class C2, class C3>
        ChildString(C1 arg1, C2 arg2, C3 arg3): std::string(arg1, arg2, arg3)
        {
            std::cout << "\tConstructor call ChildString(C1 arg1, C2 arg2, C3 arg3): " << *this << std::endl;
        }

к

template<typename... Args>
        ChildString(Args... args): std::string(args...)
        {
            std::cout 
                << "\tConstructor call ChildString(nArgs = "
                << sizeof...(Args) << "): " << *this
                << std::endl;
        }

Я слегка обижен тем, что такой хороший пример предлагает повсеместно использовать std::endl. Люди видят это, складывают в циклы и задаются вопросом, почему запись кучи строк в текстовый файл «на C++» в 5-20 раз медленнее, чем при использовании fprintf. TL; DR: используйте "\n" (добавленный к существующему строковому литералу, если он есть) и используйте std::endl только тогда, когда вам нужно сбросить буферы в файл (например, для отладки, если код выходит из строя, и вы хотите увидеть его последние слова). Я думаю, что std::endl был ошибкой дизайна, связанной с удобством: это крутой «гаджет», который делает гораздо больше, чем предполагает название.

Kuba hasn't forgotten Monica 25.11.2020 19:21

Другие вопросы по теме