Подтвердить что ты не робот

Почему явным образом вызываю конструктор в С++

Я знаю, что мы можем явно вызвать конструктор класса в С++ с помощью оператора разрешения области видимости, т.е. className::className(). Мне было интересно, где именно мне нужно сделать такой звонок.

4b9b3361

Ответ 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;
}

Это вызовет конструктор по умолчанию явно для инициализации переменной.