Я знаю, что мы можем явно вызвать конструктор класса в С++ с помощью оператора разрешения области видимости, т.е. className::className()
. Мне было интересно, где именно мне нужно сделать такой звонок.
Почему явным образом вызываю конструктор в С++
Ответ 1
Чаще всего в конструкторе дочернего класса, для которого требуются некоторые параметры:
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 )
{ }
// ...
};
... например.
Кроме этого, я не вижу утилиту. Я только называл конструктор в другом коде, когда был слишком молод, чтобы узнать, что я действительно делаю...
Ответ 2
Вы также иногда явно используете конструктор для создания временного. Например, если у вас есть класс с конструктором:
class Foo
{
Foo(char* c, int i);
};
и функции
void Bar(Foo foo);
но у вас нет Foo, вы могли бы сделать
Bar(Foo("hello", 5));
Это похоже на бросок. Действительно, если у вас есть конструктор, который принимает только один параметр, компилятор С++ будет использовать этот конструктор для выполнения неявных отбросов.
Невозможно вызвать конструктор на уже существующий объект. То есть вы не можете делать
Foo foo;
foo.Foo(); // compile error!
независимо от того, что вы делаете. Но вы можете вызывать конструктор без выделения памяти - для чего предназначено новое место размещения.
char buffer[sizeof(Foo)]; // a bit of memory
Foo* foo = new(buffer) Foo(); // construct a Foo inside buffer
Вы даете новую память, и она создает объект в этом месте вместо выделения новой памяти. Это использование считается злым и редко встречается у большинства типов кода, но распространено во встроенном коде данных и структуре данных.
Например, std::vector::push_back
использует этот метод для вызова конструктора копирования. Таким образом, нужно сделать только одну копию вместо создания пустого объекта и использования оператора присваивания.
Ответ 3
Я думаю, что сообщение об ошибке для ошибки компилятора C2585 дает наилучшую причину, по которой вам нужно будет фактически использовать оператор разрешения области видимости в конструкторе, и он отвечает с ответом Чарли:
Преобразование из класса или структуры, основанного на множественном наследовании. Если тип наследует один и тот же базовый класс более одного раза, функция преобразования или оператор должны использовать разрешение области (::), чтобы указать, какой из наследуемых классов использовать в преобразовании.
Итак, представьте, что у вас есть BaseClass, а BaseClassA и BaseClassB наследуют BaseClass, а затем DerivedClass наследует как BaseClassA, так и BaseClassB.
Если вы выполняете преобразование или перегрузку оператора для преобразования DerivedClass в BaseClassA или BaseClassB, вам нужно будет определить, какой конструктор (я думаю, что-то вроде конструктора копирования, IIRC) для использования в преобразовании.
Ответ 4
В общем случае вы не вызываете конструктор напрямую. Новый оператор вызывает его для вас или подкласс вызывает конструкторы родительского класса. В С++ базовый класс должен быть полностью сконструирован до того, как начнется конструктор производного класса.
Единственный раз, когда вы вызываете конструктор напрямую, - это крайне редкий случай, когда вы управляете памятью без использования нового. И даже тогда вы не должны этого делать. Вместо этого вы должны использовать форму размещения оператора new.
Ответ 5
Я не думаю, что вы обычно используете это для конструктора, по крайней мере, не так, как вы описываете. Однако вам это понадобится, если у вас есть два класса в разных пространствах имен. Например, чтобы указать разницу между этими двумя созданными классами, Xml::Element
и Chemistry::Element
.
Обычно имя класса используется с оператором разрешения области действия для вызова функции унаследованного родительского класса. Итак, если у вас есть класс Dog, который наследуется от Animal, и оба эти класса определяют функцию Eat() по-другому, может быть случай, когда вы хотите использовать версию Animal для еды на объекте Dog, называемом "someDog". Мой синтаксис С++ немного ржавый, но я думаю, что в этом случае вы сказали бы someDog.Animal::Eat()
.
Ответ 6
Существуют допустимые варианты использования, в которых вы хотите открыть конструкторы классов. Если вы хотите, например, сделать собственное управление памятью с помощью распределителя арены, вам понадобится двухфазная конструкция, состоящая из инициализации выделения и объекта.
Подход, который я принимаю, аналогичен подходу многих других языков. Я просто поместил свой код построения в известные общедоступные методы (Construct(), init(), что-то в этом роде) и назовите их, когда это необходимо.
Вы можете создавать перегрузки этих методов, которые соответствуют вашим конструкторам; ваши обычные конструкторы просто звонят в них. Поместите большие комментарии в код, чтобы предупредить других о том, что вы делаете это, чтобы они не добавляли важный код конструкции в неправильное место.
Помните, что существует только один метод деструктора независимо от того, какая конструктивная перегрузка была использована, поэтому сделайте ваши деструкторы надежными для неинициализированных членов.
Я рекомендую не пытаться писать инициализаторы, которые могут быть повторно инициализированы. Трудно сказать случай, когда вы смотрите на объект, в котором есть мусор, из-за неинициализированной памяти и фактического хранения реальных данных.
Самая сложная проблема связана с классами, которые имеют виртуальные методы. В этом случае компилятор обычно подключает указатель таблицы функций vtable в качестве скрытого поля в начале класса. Вы можете вручную инициализировать этот указатель, но в основном вы зависите от специфики компилятора, и это, вероятно, заставит ваших коллег смотреть на вас смешно.
Размещение новых нарушено во многих отношениях; в конструкции/уничтожении массивов - это один случай, поэтому я, как правило, не использую его.
Ответ 7
Рассмотрим следующую программу.
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;
}
Это вызовет конструктор по умолчанию явно для инициализации переменной.