Зачем явно вызывать конструктор в C ++ - PullRequest
21 голосов
/ 06 мая 2009

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

Ответы [ 7 ]

44 голосов
/ 06 мая 2009

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

class Foo
{
    Foo(char* c, int i);
};

и функция

void Bar(Foo foo);

но у вас нет Foo, вы могли бы сделать

Bar(Foo("hello", 5));

Это похоже на актерский состав. Действительно, если у вас есть конструктор, который принимает только один параметр, компилятор C ++ будет использовать этот конструктор для выполнения неявных приведений.

не законно вызывать конструктор для уже существующего объекта. То есть вы не можете сделать

Foo foo;
foo.Foo();  // compile error!

независимо от того, что вы делаете. Но вы можете вызывать конструктор без выделения памяти - для этого размещение new .

char buffer[sizeof(Foo)];      // a bit of memory
Foo* foo = new(buffer) Foo();  // construct a Foo inside buffer

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

Например, std::vector::push_back использует эту технику для вызова конструктора копирования. Таким образом, ему нужно сделать только одну копию вместо создания пустого объекта и использования оператора присваивания.

13 голосов
/ 06 мая 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 )
    { }

// ...
};

... например.

Кроме этого, я не вижу утилиту. Я вызывал конструктор в другом коде, только когда был слишком молод, чтобы знать, что я на самом деле делал ...

3 голосов
/ 06 мая 2009

Я думаю, что сообщение об ошибке для ошибки компилятора C2585 дает лучшую причину, по которой вам нужно было бы на самом деле использовать оператор разрешения области действия в конструкторе, и это соответствует ответу Чарли:

Преобразование из класса или типа структуры на основе множественного наследования. Если тип наследует один и тот же базовый класс более одного раза, функция или оператор преобразования должны использовать разрешение области (: :), чтобы указать, какой из унаследованных классов использовать при преобразовании.

Итак, представьте, что у вас есть BaseClass, а BaseClassA и BaseClassB оба наследуют BaseClass, а затем DerivedClass наследует и BaseClassA, и BaseClassB.

Если вы выполняете преобразование или перегрузку оператора для преобразования DerivedClass в BaseClassA или BaseClassB, вам необходимо определить, какой конструктор (я думаю, что-то вроде конструктора копирования, IIRC) использовать в преобразовании.

2 голосов
/ 06 мая 2009

Как правило, вы не вызываете конструктор напрямую. Оператор new вызывает его для вас, или подкласс вызывает конструкторы родительского класса. В C ++ базовый класс гарантированно будет полностью сконструирован до запуска конструктора производного класса.

Единственный раз, когда вы вызываете конструктор напрямую, это крайне редкий случай, когда вы управляете памятью без использования new. И даже тогда вы не должны этого делать. Вместо этого вам следует использовать форму размещения оператора new.

0 голосов
/ 11 декабря 2013

Рассмотрим следующую программу.

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;
}

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

0 голосов
/ 13 августа 2010

Существуют допустимые варианты использования, когда вы хотите выставить классы конструкторам. Если вы хотите выполнить собственное управление памятью, например, с помощью распределителя арены, вам потребуется двухфазная конструкция, состоящая из выделения и инициализации объекта.

Подход, который я использую, похож на подход многих других языков. Я просто помещаю свой код конструкции в общеизвестные общедоступные методы ( Construct () , init () , что-то в этом роде) и вызываю их напрямую, когда это необходимо.

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

Помните, что существует только один метод деструктора, независимо от того, какая перегрузка конструкции была использована, поэтому сделайте ваши деструкторы устойчивыми к неинициализированным элементам.

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

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

Размещение нового нарушено во многих отношениях; в построении / уничтожении массивов есть один случай, поэтому я склонен его не использовать.

0 голосов
/ 06 мая 2009

Я не думаю, что вы обычно используете это для конструктора, по крайней мере, не так, как вы описываете. Однако он понадобится вам, если у вас есть два класса в разных пространствах имен. Например, чтобы указать разницу между этими двумя составными классами, Xml::Element и Chemistry::Element.

Обычно имя класса используется с оператором разрешения области для вызова функции родительского объекта унаследованного класса. Таким образом, если у вас есть класс Dog, который наследует от Animal, и оба этих класса по-разному определяют функцию Eat (), может быть случай, когда вы захотите использовать версию Eat в Animal для объекта Dog с именем «someDog». Мой синтаксис C ++ немного ржавый, но я думаю, что в этом случае вы скажете someDog.Animal::Eat().

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...