Я знаю, что мы можем явно вызвать конструктор класса в C++, используя оператор разрешения области видимости, то есть className::className()
. Мне было интересно, где именно мне нужно сделать такой звонок.
Зачем явно вызывать конструктор в C++
- Неправильно говорить, что вы можете вызывать конструктор напрямую. В стандарте явно указано (12.1/1): конструкторы не имеют имен. Вы можете вызывать конструктор только через другие конструкции, такие как приведение стиля функции или новое размещение. 06.05.2009
Ответы:
Чаще всего в конструкторе дочернего класса требуются некоторые параметры:
class BaseClass
{
public:
BaseClass( const std::string& name ) : m_name( name ) { }
const std::string& getName() const { return m_name; }
private:
const std::string m_name;
//...
};
class DerivedClass : public BaseClass
{
public:
DerivedClass( const std::string& name ) : BaseClass( name ) { }
// ...
};
class TestClass :
{
public:
TestClass( int testValue ); //...
};
class UniqueTestClass
: public BaseClass
, public TestClass
{
public:
UniqueTestClass()
: BaseClass( "UniqueTest" )
, TestClass( 42 )
{ }
// ...
};
... Например.
Кроме этого, я не вижу полезности. Я вызывал конструктор в другом коде только тогда, когда был слишком молод, чтобы понимать, что я на самом деле делаю...
Вы также иногда явно используете конструктор для создания временного объекта. Например, если у вас есть класс с конструктором:
class Foo
{
Foo(char* c, int i);
};
и функция
void Bar(Foo foo);
но у тебя нет Foo рядом, ты мог бы сделать
Bar(Foo("hello", 5));
Это как литье. Действительно, если у вас есть конструктор, принимающий только один параметр, компилятор C++ будет использовать этот конструктор для выполнения неявного приведения типов.
Недопустимо вызывать конструктор для уже существующего объекта. То есть вы не можете сделать
Foo foo;
foo.Foo(); // compile error!
не важно, что ты предпримешь. Но вы можете вызвать конструктор без выделения памяти — для этого и предназначен placement new.
char buffer[sizeof(Foo)]; // a bit of memory
Foo* foo = new(buffer) Foo(); // construct a Foo inside buffer
Вы даете new немного памяти, и он создает объект в этом месте вместо выделения новой памяти. Такое использование считается злом и редко встречается в большинстве типов кода, но распространено во встроенном коде и коде структур данных.
Например, std::vector::push_back
использует эту технику для вызова конструктора копирования. Таким образом, нужно сделать только одну копию вместо создания пустого объекта и использования оператора присваивания.
<typename>(ctor-arg list)
, и, если быть педантичным, это не то же самое, что <typename>::<typename>(ctor-arg list)
. Синтаксис просто создает впечатление, что вы вызываете конструктор. На самом деле вы никогда не вызываете конструктор как функцию. 14.03.2011 <typename>::<typename> <var-name>(ctor-arg list)
? 28.04.2013 Я думаю, что сообщение об ошибке компилятора C2585 дает наилучшую причину, по которой вам действительно нужно использовать оператор разрешения области действия в конструкторе, и это соответствует ответу Чарли:
Преобразование из типа класса или структуры на основе множественного наследования. Если тип наследует один и тот же базовый класс более одного раза, функция или оператор преобразования должны использовать разрешение области видимости (::), чтобы указать, какой из унаследованных классов использовать при преобразовании.
Итак, представьте, что у вас есть BaseClass, и BaseClassA, и BaseClassB оба наследуют BaseClass, а затем DerivedClass наследует и BaseClassA, и BaseClassB.
Если вы выполняете преобразование или перегрузку оператора для преобразования DerivedClass в BaseClassA или BaseClassB, вам нужно будет определить, какой конструктор (я думаю, что-то вроде конструктора копирования, IIRC) использовать в преобразовании.
Как правило, вы не вызываете конструктор напрямую. Новый оператор вызывает его для вас, или подкласс вызывает конструкторы родительского класса. В C++ гарантируется, что базовый класс будет полностью сконструирован до запуска конструктора производного класса.
Единственный случай, когда вы вызовете конструктор напрямую, — это крайне редкий случай, когда вы управляете памятью без использования new. И даже тогда не стоит этого делать. Вместо этого вы должны использовать форму размещения оператора new.
Я не думаю, что вы обычно используете это для конструктора, по крайней мере, не так, как вы описываете. Однако вам это понадобится, если у вас есть два класса в разных пространствах имен. Например, чтобы указать разницу между этими двумя искусственными классами, Xml::Element
и Chemistry::Element
.
Обычно имя класса используется с оператором разрешения области видимости для вызова функции родительского класса унаследованного класса. Итак, если у вас есть класс Dog, который наследуется от Animal, и оба этих класса определяют функцию Eat() по-разному, может возникнуть случай, когда вы захотите использовать версию eat для животных на объекте Dog с именем «someDog». Мой синтаксис C++ немного заржавел, но я думаю, что в этом случае вы бы сказали someDog.Animal::Eat()
.
Существуют допустимые варианты использования, когда вы хотите предоставить конструкторы классов. Если вы хотите самостоятельно управлять памятью, например, с помощью распределителя арены, вам потребуется двухэтапная конструкция, состоящая из выделения памяти и инициализации объекта.
Подход, который я использую, аналогичен подходу многих других языков. Я просто помещаю свой код построения в хорошо известные общедоступные методы (Construct(), init() и тому подобное) и вызываю их напрямую, когда это необходимо.
Вы можете создавать перегрузки этих методов, соответствующие вашим конструкторам; ваши обычные конструкторы просто вызывают их. Добавляйте в код большие комментарии, чтобы предупредить других, что вы делаете это, чтобы они не добавили важный строительный код в неположенном месте.
Помните, что существует только один метод деструктора, независимо от того, какая перегрузка конструкции использовалась, поэтому сделайте ваши деструкторы устойчивыми к неинициализированным членам.
Я рекомендую не пытаться писать инициализаторы, которые могут повторно инициализироваться. Трудно определить случай, когда вы смотрите на объект, в котором просто есть мусор из-за неинициализированной памяти, а не фактических данных.
Самая сложная проблема возникает с классами, имеющими виртуальные методы. В этом случае компилятор обычно вставляет указатель таблицы функций vtable в качестве скрытого поля в начале класса. Вы можете вручную инициализировать этот указатель, но в основном вы зависите от конкретного поведения компилятора, и это, вероятно, заставит ваших коллег смотреть на вас смешно.
Размещение new нарушено во многом; при построении/уничтожении массивов - это один случай, поэтому я его не использую.
Рассмотрим следующую программу.
template<class T>
double GetAverage(T tArray[], int nElements)
{
T tSum = T(); // tSum = 0
for (int nIndex = 0; nIndex < nElements; ++nIndex)
{
tSum += tArray[nIndex];
}
// Whatever type of T is, convert to double
return double(tSum) / nElements;
}
Это приведет к явному вызову конструктора по умолчанию для инициализации переменной.