C ++ - конструирование объекта внутри класса - PullRequest
22 голосов
/ 12 мая 2009

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

class Foo
{
    //stuff
};

class Bar
{
    Foo foo;
};

Таким образом, Bar содержит полный объект Foo, а не просто ссылку или указатель. Этот объект инициализируется его конструктором по умолчанию? Нужно ли явно вызывать его конструктор, и если да, то как и где?

Спасибо.

Ответы [ 9 ]

22 голосов
/ 12 мая 2009

Он будет инициализирован его конструктором по умолчанию. Если вы хотите использовать другой конструктор, у вас может быть что-то вроде этого:

class Foo
{
    public: 
    Foo(int val) { }
    //stuff
};

class Bar
{
    public:
    Bar() : foo(2) { }

    Foo foo;
};
16 голосов
/ 12 мая 2009

Конструкция - довольно сложная тема в C ++. Простой ответ - , это зависит . Инициализируется ли Foo или нет, зависит от определения самого Foo. О втором вопросе: как заставить Bar инициализировать Foo: списки инициализации являются ответом.

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

Если Foo не имеет заданного пользователем конструктора по умолчанию, Foo будет неинициализирован. Чтобы быть более точным: каждый член Bar или Foo, в котором отсутствует определяемый пользователем конструктор по умолчанию, будет неинициализирован сгенерированным компилятором конструктором по умолчанию Bar :

class Foo {
   int x;
public:
   void dump() { std::cout << x << std::endl; }
   void set() { x = 5; }
};
class Bar {
   Foo x;
public:
   void dump() { x.dump(); }
   void set() { x.set(); } 
};
class Bar2
{
   Foo x;
public:
   Bar2() : Foo() {}
   void dump() { x.dump(); }
   void set() { x.set(); }
};
template <typename T>
void test_internal() {
   T x;
   x.dump();
   x.set();
   x.dump();
}
template <typename T>
void test() {
   test_internal<T>();
   test_internal<T>();
}
int main()
{
   test<Foo>(); // prints ??, 5, 5, 5, where ?? is a random number, possibly 0
   test<Bar>(); // prints ??, 5, 5, 5
   test<Bar2>(); // prints 0, 5, 0, 5
}

Теперь, если Foo имеет определяемый пользователем конструктор, он будет инициализирован всегда, независимо от того, имеет ли Bar инициализированный пользователем конструктор или нет. Если в Bar есть пользовательский конструктор, который явно вызывает (возможно, неявно определенный) конструктор Foo, то Foo фактически будет инициализирован. Если список инициализации Bar не вызывает конструктор Foo, то это будет эквивалентно случаю, когда у Bar не было определяемого пользователем конструктора.

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

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

6 голосов
/ 12 мая 2009

Существует четыре функции, которые сгенерирует компилятор C ++ для каждого класса, если это возможно, и если вы их не предоставляете: конструктор по умолчанию, конструктор копирования, оператор присваивания и деструктор. В Стандарте C ++ (глава 12, «Специальные функции») они упоминаются как «неявно объявленные» и «неявно определенные». Они будут иметь публичный доступ.

Не путайте «неявно определенные» с «по умолчанию» в конструкторе. Конструктор по умолчанию - тот, который можно вызывать без аргументов, если он есть. Если вы не предоставите конструктор, будет задан неявный конструктор по умолчанию. Он будет использовать конструкторы по умолчанию для каждого базового класса и члена данных.

Итак, что происходит, так это то, что класс Foo имеет неявно определенный конструктор по умолчанию, а Bar (который, похоже, не имеет пользовательского конструктора) использует неявно определенный конструктор по умолчанию, который вызывает конструктор Foo по умолчанию.

Если вы действительно хотите написать конструктор для Bar, вы можете упомянуть foo в его списке инициализаторов, но, поскольку вы используете конструктор по умолчанию, вам не нужно его указывать.

Помните, что если вы пишете конструктор для Foo, компилятор не будет автоматически генерировать конструктор по умолчанию, поэтому вам придется указать его, если он вам нужен. Следовательно, если вы поместите что-то вроде Foo(int n); в определение Foo и не будете явно писать конструктор по умолчанию (либо Foo();, либо Foo(int n = 0);), вы не сможете получить Bar в его нынешнем виде, так как он не мог использовать конструктор Foo по умолчанию. В этом случае вам понадобится конструктор типа Bar(int n = 0): foo(n) {}, в котором конструктор Bar инициализирует Foo. (Обратите внимание, что Bar(int n = 0) {foo = n;} или подобное не будет работать, так как конструктор Bar сначала попытается инициализировать foo, и это не получится.)

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

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

Bar::Bar() : foo(42) {}

Это, конечно, при условии, что вы добавляете Foo :: Foo (int) к коду:)

1 голос
/ 15 августа 2018

Конструктор, предназначенный для установки начального состояния объекта. В случае иерархии наследования объекты базового класса будут построены в порядке иерархии наследования (отношение IS-A в терминологии ОО), за которым следуют объекты производного класса.

Точно так же, когда объект внедряется в другой объект (отношение HAS-A в терминах или содержании OO), конструкторы встроенного объекта вызываются в порядке их объявления.

Компилятор анализирует все члены класса B, а затем создает код для каждого метода. На этом этапе ему известны все члены и их порядок, и он будет составлять элементы по порядку, используя конструкторы по умолчанию, если не указано иное. Таким образом, foo создается без явного вызова конструктора по умолчанию. Что вам нужно сделать, это просто создать объект Bar.

1 голос
/ 12 мая 2009

Если не указано иное, foo инициализируется с использованием конструктора по умолчанию. Если вы хотите использовать какой-то другой конструктор, вам нужно сделать это в списке инициализатора для Bar:

Bar::Bar( int baz ) : foo( baz )
{
    // Rest of the code for Bar::Bar( int ) goes here...
}
1 голос
/ 12 мая 2009

Полный объект. Нет, это конструктор по умолчанию в конструкторе по умолчанию Bar.

Теперь, если у Foo есть конструктор, который принимает, скажем, только int. Вам понадобится конструктор в Bar, чтобы вызвать конструктор Foo и сказать, что это такое:

class Foo {
public:
    Foo(int x) { .... }
};

class Bar {
public:
    Bar() : foo(42) {}

    Foo foo;
};

Но если бы Foo имел конструктор по умолчанию Foo (), компилятор автоматически генерирует конструктор Bar, и это вызвало бы Foo по умолчанию (т.е. Foo ())

1 голос
/ 12 мая 2009

Таким образом, Bar содержит полный объект Foo, а не просто ссылку или указатель. Этот объект инициализирован его конструктором по умолчанию?

Если Foo имеет ctor по умолчанию, объект типа Foo будет использовать ctor по умолчанию при создании объекта типа Bar. В противном случае вам нужно вызвать Foo ctor самостоятельно, или ваш Bar ctor заставит ваш компилятор жаловаться на жадность.

например:

class Foo {
public:
 Foo(double x) {}
};

class Bar  {
 Foo x;
};

int main() {
 Bar b;
}

Выше будет иметь компилятор жаловаться что-то вроде:

"В конструкторе 'Bar :: Bar ()': строка 5: ошибка: нет соответствующей функции для вызова 'Foo :: Foo ()'

Нужно ли явно вызывать его конструктор, и если да, то как и где?

Измените приведенный выше пример следующим образом:

class Foo {
 public:
  Foo(double x) {} // non-trivial ctor
};

class Bar  {     
 Foo x;
public:
  Bar() : x(42.0) {} // non-default ctor, so public access specifier required
};

int main() {
 Bar b;
}
0 голосов
/ 12 мая 2009

Вам не нужно явно вызывать конструктор по умолчанию в C ++, он будет вызван для вас. Если вы хотите вызвать другой конструктор, вы можете сделать это:

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