Конструктор и инициализация пользовательских классов / объектов - PullRequest
9 голосов
/ 21 марта 2012

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

У меня есть пользовательский класс

class myClass_A
{
public:
    myClass_A();          // Constructor
    myFunction_A();       // Some function from Class A
};

Теперь у меня есть другой пользовательский класс, в котором есть член типа myClass_A

class myClass_B
{
public:
    myFunction_B();       // Some function from Class B

private:
    myClass_A m_instance; // Instance of Class A
}

Теперь myFunction_B() хочет вызвать метод myFunction_A() из m_instance вроде этого:

myClass_B::myFunction_B()
{
    m_instance.myFunction_A();
}

Теперь, если я скомпилирую свой код (который в основном похож на приведенный выше пример), он будет выполнен без каких-либо предупреждений или ошибок.Поэтому мои вопросы будут:

A.Будет ли вызываться конструктор в этом примере?

B.Могу ли я на самом деле вызывать методы из неинициализированного объекта?

C.Предполагая, что конструктор не вызывается, но я все еще могу вызывать методы этого объекта -> Это по-прежнему означает, что члены моего класса не инициализированы?

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

Ответы [ 5 ]

7 голосов
/ 21 марта 2012

Это очень хорошие и важные вопросы.

Относительно A:

Перед выполнением тела вашего конструктора C ++ генерирует код, который автоматически вызывает конструктор по умолчанию всех агрегированных (т.е. членских) объектов вашего класса.По сути, он преобразует следующий код:

class myClass_B {
public:
    myClass_B()
    {
        m_instance.foo();
        m_pInstance->foo();
    }
private:
    myClass_A m_instance;
    myClass_A* m_pInstance;
};

в следующий код:

class myClass_B {
public:
    myClass_B()
        : m_instance()
        , m_pInstance()
    {
        m_instance.foo();
        m_pInstance->foo();
    }
private:
    myClass_A m_instance;
    myClass_A* m_pInstance;
};

Две строки, автоматически добавленные компилятором, называются список инициализатора , он вызывает конструктор по умолчанию для каждого агрегатного объекта до того, как будет выполнено тело вашего конструктора.Обратите внимание, что второй, m_pInstance() вызывает "конструктор по умолчанию указатель ", который создает неинициализированный указатель ;это почти всегда не то, что вы хотите.Ниже описано, как это исправить.

Теперь давайте предположим, что конструктор myClass_A имеет подпись myClass_A(int someNumber), то есть он принимает аргумент.Затем C ++ не может автоматически сгенерировать список инициализатора для myClass_B, поскольку он не знает, какое число передать конструктору myClass_A.Он выдаст вам ошибку компилятора, вероятно, жалуясь на отсутствующий конструктор по умолчанию для myClass_A.Вам нужно будет написать список инициализаторов самостоятельно, например:

class myClass_B {
public:
    myClass_B()
        : m_instance(21)
        , m_pInstance(new myClass_A(21))
    {
        m_instance.foo();
        m_pInstance->foo();
    }
private:
    myClass_A m_instance;
    myClass_A* m_pInstance;
};

Это правильный код, который вызывает конструктор myClass_A со значением 21 для параметра someNumber.Здесь также показано, как правильно инициализировать указатель: сделать так, чтобы он указывал на какой-то вновь выделенный объект.

Относительно B:

В отличие от некоторых других, вы можете!(Попробуйте)

Но это приводит к неожиданному поведению, а это не то, что вам нужно.(Включая то, что он может делать то, что вы хотите, только когда планеты выровнены правильно.) Скорее всего, он рухнет, но не гарантированно рухнет.Это может привести вас к долгим отладочным ночам.Если ваш компилятор умен, он может распознать это и предупредить вас, но не выдаст ошибку.

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

Относительно C:

Да.Смотрите B для деталей.Интересно то, что если метод, который вы вызываете, не использует указатель «this» (это включает в себя не использование какой-либо переменной атрибута и не вызовет метод, который использует переменную атрибута), ваш метод гарантированно будет работать.Когда вы вызываете метод для неинициализированного объекта, происходит то, что «this» объект в методе (т.е. все атрибутные переменные тоже) является случайной памятью.Код метода будет выполняться, но использовать случайную память, и это то, что не работает.

Надеюсь, это немного прояснит ситуацию.

4 голосов
/ 21 марта 2012

Будет ли вызываться конструктор в этом примере?

Да
Конструкторы для myClass_B будут вызваны после вызова конструктора myClass_A() завершено.
Обычно Вы инициализируете объект myClass_B в конструкторе my_class_A, используя
Список инициализирующих элементов .

Могу ли я на самом деле вызывать методы из неинициализированного объекта?

Когда объект создается, всегда вызывается его конструктор. Если вы не создаете объект, вы не можете вызвать какой-либо методв теме.Это и есть цель конструкторов создать объект и инициализировать его.
Так что если у вас есть объект, он никогда не инициализируется.

Если вы ссылаетесь на указатели, то указатели сами по себе не являются объектами, которые они могут указывать на действительный или недействительный объект.Отключение указателя (для вызова функции-члена или чего-либо еще), не указывающего на допустимый объект, приведет к неопределенному поведению .

Предполагается, что конструктор не вызывается, но я все еще могу вызывать методы этого объекта -> Это по-прежнему означает, что члены моего класса не инициализированы?

Ответ на второй вопрос отвечает на это.

2 голосов
/ 21 марта 2012

A. да: конструкторы членов вызываются перед конструктором содержащего класса. Б. Да, но результатом является неопределенное поведение (= все может случиться; даже если кажется, что работает).

2 голосов
/ 21 марта 2012

Члены класса автоматически инициализируются компилятором с помощью конструктора по умолчанию, если вы не укажете иначе в конструкторе вашего собственного класса список инициализации . Так что да, конструктор будет называться.

Вы можете проверить это самостоятельно:

  • если конструктор напечатает сообщение (которое вы увидите), или
  • сделать конструктор частным или объявить конструктор с аргументами, чтобы конструктор по умолчанию больше не генерировался автоматически (компилятор откажется компилировать, так как он больше не может конструировать агрегатный объект по умолчанию)

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

0 голосов
/ 21 марта 2012

A. Конструктор для вашего класса myClass_A будет вызываться в тот момент, когда создается экземпляр объекта класса myClass_B.

B. Вы не можете вызывать методы не инициализированных объектов, например

myClass_A *pObj;
pObj->myFunction_A();

выдаст вам исключение.

C. Вы никогда не сможете вызвать метод успешно, пока объект не будет полностью построен.

...