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





Конструкторы базового класса вызываются автоматически, если у них нет аргументов. Если вы хотите вызвать конструктор суперкласса с аргументом, вы должны использовать список инициализации конструктора подкласса. В отличие от 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/…
оператор двоеточие:, который вы использовали для вызова конструктора суперкласса перед созданием экземпляра конструктора дочернего класса, я полагаю, это также верно для методов?
@hagubear, действительно только для конструкторов, AFAIK
Когда вы создаете экземпляр объекта SubClass, скажем, SubClass anObject(1,2), передается ли 1 в SuperClass(foo) (становится аргументом параметра foo)? Я просматривал документы сверху и снизу, но ни один из них окончательно не заявил, что аргументы конструктора SubClass могут быть переданы в качестве аргументов конструктору SuperClass.
@Gnuey, обратите внимание на часть : SuperClass(foo). foo явно передается конструктору суперкласса.
Да, заметил эту часть. В коде подразумевалось, что просто нужно, чтобы это было явно указано в письменной форме. Спасибо.
Должна ли быть вызвана часть конструктора суперкласса из файла initializatoin (файл cpp) или достаточно объявить его в файле .h?
@andig: вызов находится там, где конструктор / определен /
Если у вас есть конструктор без аргументов, он будет вызван до выполнения конструктора производного класса.
Если вы хотите вызвать базовый конструктор с аргументами, вы должны явно написать это в производном конструкторе следующим образом:
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 заменой тела конструктора?
Да. Я изменил формулировку раздела и исправил ошибку (ключевое слово try стоит перед списком инициализации). Я должен был поискать его вместо того, чтобы писать по памяти, это не то, что часто используется :-)
Благодарим за включение синтаксиса try / catch для инициализаторов. Я использую C++ 10 лет и впервые вижу такое.
Я должен признать, что использую C++ долгое время, и это первый раз, когда я вижу, что try / catcn в списке конструкторов.
Я мог бы сказать, что тело функции «входит» в блок try - таким образом, любое тело, следующее за инициализаторами, также будет иметь свои исключения.
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_ внутри тела конструктора, потому что вы экономите дополнительные затраты на назначение.
Имейте в виду, что элементы данных всегда инициализируются в том порядке, в котором они объявлены в определении класса, независимо от их порядка в списке инициализации. Чтобы избежать странных ошибок, которые могут возникнуть, если ваши элементы данных зависят друг от друга, вы всегда должны следить за тем, чтобы порядок членов был одинаковым в списке инициализации и в определении класса. По той же причине конструктор базового класса должен быть первым элементом в списке инициализации. Если вы его полностью опустите, конструктор по умолчанию для базового класса будет вызываться автоматически. В этом случае, если базовый класс не имеет конструктора по умолчанию, вы получите ошибку компилятора.
Подождите ... Вы говорите, что инициализаторы экономят на назначениях. Но разве в них не выполняются те же задания, если они вызваны?
Неа. Инициализация и назначение - разные вещи. Когда вызывается конструктор, он пытается инициализировать каждый член данных тем, что, по его мнению, является значением по умолчанию. В списке инициализации вы можете указать значения по умолчанию. Таким образом, в любом случае вы несете затраты на инициализацию.
И если вы используете присваивание внутри тела, вы все равно несете стоимость инициализации, а затем стоимость присваивания сверх этого.
Этот ответ был полезен, потому что он показал вариант синтаксиса, в котором есть заголовок и исходный файл, а список инициализации в заголовке не нужен. Очень полезно, спасибо.
Все упоминали вызов конструктора через список инициализации, но никто не сказал, что конструктор родительского класса может быть вызван явно из тела конструктора производного члена. См., Например, вопрос Вызов конструктора базового класса из тела конструктора подкласса. Дело в том, что если вы используете явный вызов родительского класса или конструктора суперкласса в теле производного класса, на самом деле это просто создает экземпляр родительского класса, а не вызывает конструктор родительского класса для производного объекта. . Единственный способ вызвать конструктор родительского класса или суперкласса для объекта производного класса - через список инициализации, а не в теле конструктора производного класса. Так что, возможно, это не следует называть «вызовом конструктора суперкласса». Я поместил этот ответ сюда, потому что кто-то может запутаться (как и я).
Этот ответ несколько сбивает с толку, хотя я прочитал его пару раз и взглянул на вопрос, связанный с. Я думаю, что он говорит о том, что если вы используете явный вызов родительского класса или конструктора суперкласса в теле производного класса, на самом деле это просто создает экземпляр родительского класса, а не вызывает родительский класс. конструктор производного объекта. Единственный способ вызвать конструктор родительского класса или суперкласса для объекта производного класса - через список инициализации, а не в теле конструктора производного класса.
@Richard Chambers: Может быть, это сбивает с толку, поскольку английский не мой родной язык, но вы точно описали то, что я пытался сказать.
«конструктор родительского класса может быть вызван явно из тела конструктора производного члена», что явно неверно для рассматриваемого экземпляра, если вы не имеете в виду размещение new, и даже в этом случае это неверно, потому что вам нужно сначала разрушить экземпляр. Например. MyClass::MyClass() { new (this) BaseClass; /* UB, totally wrong */ } - это синтаксис C++ для явный вызов конструктора. Так выглядит «вызов конструктора». Так что тот факт, что за этот абсурдно неправильный ответ получил поддержку, для меня является полной загадкой.
Я считаю, что большинство ответов на этот вопрос, на который вы ссылаетесь, являются нежелательными или уходящими в сторону. Я написал ответ, которого все время не хватало, кажется. Я не удивлен, что это кто-то может запутаться, пытаясь понять что-нибудь из вашей ссылки ... Я тоже был сбит с толку. Это легкая вещь, но люди пишут об этом, как будто это волшебство. Слепой ведет слепого. Явный "вызов" конструктора осуществляется с размещением нового синтаксиса!MyClass() - это не какой-то "звонок"! Он имеет то же значение, что и, например, int(), и это создает ценность!
Никто не упомянул последовательность вызовов конструктора, когда класс является производным от нескольких классов. Последовательность указана при создании классов.
Если об этом никто не говорил, где это было упомянуто?
@EJP так как речь идет о правилах вызова, в ответе стоит упомянуть последовательность вызовов
Если у вас есть параметры по умолчанию в базовом конструкторе, базовый класс будет вызываться автоматически.
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 и инициализация равных скобок делают аргументы по умолчанию менее необходимыми (когда они уже были похожи на запах кода во многих примерах).
Если вы просто хотите передать все аргументы конструктора базовому классу (= parent), вот минимальный пример.
При этом используются шаблоны для перенаправления каждого вызова конструктора с 1, 2 или 3 аргументами в родительский класс std::string.
Код
#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 был ошибкой дизайна, связанной с удобством: это крутой «гаджет», который делает гораздо больше, чем предполагает название.
Просто придирки: в C++ нет "суперкласса", фактически, стандарт вообще не упоминает об этом. Эта формулировка происходит из Java (скорее всего). Используйте «базовый класс» в C++. Я предполагаю, что супер подразумевает единственного родителя, а C++ допускает множественное наследование.