2009-05-11 6 views
19

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

class Foo 
{ 
    //stuff 
}; 

class Bar 
{ 
    Foo foo; 
}; 

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

Спасибо.

+4

Я знаком с C++, и у меня все еще есть некоторые сомнения в такой степени каждый так часто. Более того, большинство ответов прямо указывают, что будет вызываться конструктор по умолчанию Foo, и факт в том, что он зависит от определения самого Foo. Является ли он предоставленным пользователем или неявным конструктором по умолчанию? Имеет ли он какие-либо атрибуты частного участника? Инициализация на C++ не проста. –

+2

Очень забавно, что @xtofl просит плакат удалить «Я знаком с C++» ... Может быть, большинство людей не так знакомы с C++, когда почти все ответы ошибочны. На самом деле инициализация тяжелая, некоторые из людей, которые ответили, доказали свои знания на C++ @JaredPar, @dirkgently, @David Thornley и все же не смогли. –

ответ

17

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

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

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

    Foo foo; 
}; 
+0

Синтаксическая ошибка: вам нужно двоеточие (:) после ключевого слова public. – dirkgently

+2

И после двоеточия (;) после закрытия} каждого класса. Я обвиняю Java. – richq

+0

спасибо за исправления синтаксиса. Я обвиняю C#. –

1

So Bar constains a full Foo object, not just a reference or pointer. Is this object initialized by its default constructor?

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

Например:

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

class Bar { 
Foo x; 
}; 

int main() { 
Bar b; 
} 

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

"In constructor 'Bar::Bar()': Line 5: error: no matching function for call to 'Foo::Foo()'

Do I need to explicitly call its constructor, and if so, how and where ?

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

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

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

+0

Это то, что я имел в виду по умолчанию ctor. – dirkgently

+0

Возможно ли инициализировать объект без использования списков инициализации? Как и в, вы можете инициализировать объект внутри {} конструктора? –

2

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

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

Это, конечно, если вы добавить Foo :: Foo (INT) в коде :)

+1

Я вижу, что большинство из вас придумали «42» в ваших примерах. Откуда это? – ichiban

+7

Это ссылка на H2G2 - ответ на конечный вопрос жизни, Вселенную и все. Читайте: http://en.wikipedia.org/wiki/42_(number)#In_The_Hitchhiker.27s_Guide_to_the_Galaxy. Еще лучше прочитать Дугласа Адамса. – dirkgently

+0

@ichiban, @dirkg Правильно. – JaredPar

1

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

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

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

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

    Foo foo; 
}; 

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

+0

Я вижу, что большинство из вас придумали «42» в ваших примерах. Откуда это? – ichiban

+0

Не работает, все ctors являются частными по умолчанию. – dirkgently

+0

Упс. Исправлена. Я склоняюсь к этому, когда размышляю/говорю о других понятиях. – Macke

1

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

Bar::Bar(int baz) : foo(baz) 
{ 
    // Rest of the code for Bar::Bar(int) goes here... 
} 
+0

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

+0

Возможно ли инициализировать объект без использования списков инициализации? Как и в, вы можете инициализировать объект внутри {} конструктора? –

+0

@JustinLiang, нет, как только тело конструктора выполняется, любые объекты, которые являются частью инициализированного класса, были инициализированы. Если у объекта нет конструктора по умолчанию, требуется вызвать конструктор в списке инициализаторов. Однако вы можете использовать назначение для изменения объектов и данных в классе, но имейте в виду, что это менее эффективно, поскольку объект получает инициализацию и затем перезаписывается с назначением. – Naaff

0

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

Foo foo(somearg) 
+0

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

4

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

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

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

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

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

+0

Неявно определенный конструктор в Bar НЕ будет вызывать неявно определенный конструктор в Foo. Если Foo имеет определяемый пользователем конструктор, то Bar неявно определяемый конструктор (на этот раз да) вызовет определяемый пользователем конструктор в Foo. Это самый полный ответ до сих пор: +1 –

+0

Уверен? Согласно 12.6.2 (4), нестатический член данных инициализируется с его конструктором по умолчанию, если не указан в списке инициализаторов, а в 8.5 (5) вызывается конструктор по умолчанию, если он не является POD (обычные обычные данные, в стандартном), и в этом случае он инициализируется нулем. Итак, я думаю, вопрос заключается в том, является ли Foo типом POD, который мы не видим. Если это тип POD, все инициализируются нулем; если у него есть одна из вещей, которые отмечают его как не-POD, он инициализируется по умолчанию и поэтому вызывает неявно определенный конструктор по умолчанию. –

12

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

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

Если 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. Если объект не инициализирован, его память сохранит старое содержимое.

+0

Знаете ли вы, почему при запуске теста () выход равен?, 5, 5, 5 вместо?, 5,?, 5 не исключает объект Foo (x), когда test_internal возвращает ? Аналогично для теста ()? – user1084113

+0

@ user1084113: Поскольку это неопределенное поведение, вы можете получить либо, но тест злоупотребляет знаниями о том, как компиляторы/архитектуры обычно используют стек. В принципе, когда функция вводится, она захватывает кусок стека для своих внутренних переменных, который затем освобождается, когда заканчивается функция (в зависимости от соглашения о вызове он может быть вызывающим или функцией, которая обновляет указатель стека). Второй вызов функции использует тот же блок памяти для локальных переменных, заложенный в том же порядке. «X» не инициализирован, имея значение, назначенное в последнем запуске функции. –

Смежные вопросы