Веселые конструкторы C ++ - создание Foo с копией самого себя - PullRequest
8 голосов
/ 24 февраля 2010

у меня есть:

class Foo;

class Bar {
  Foo foo;
  Bar(): foo(foo) {};
}

Bar bar;

На данный момент это

bar.foo // <--- how is this initialized?

[Этот вопрос возник из-за ошибочной реализации указателя с пересчетом; Я мог бы поклясться, что каждый указатель указывает на что-то ненулевое; но я получил указатель, указывающий на что-то NULL.]

Ответы [ 4 ]

12 голосов
/ 24 февраля 2010

foo полностью инициализируется после ввода тела конструктора (это гарантированный общий случай; особенно после завершения инициализации в списке инициализации.)

В вашем случае вы создаете копию из неструктурированного объекта. Это приводит к неопределенному поведению, согласно §12.7 / 1 (спасибо, gf):

Для объекта типа класса, отличного от POD (раздел 9), перед тем, как конструктор начнет выполнение и после того, как деструктор закончит выполнение, обращение к любому нестатическому члену или базовому классу объекта приведет к неопределенному поведению.

На самом деле, он дает такой пример:

struct W { int j; };
struct X : public virtual W { };
struct Y {
    int *p;
    X x;
    Y() : p(&x.j) // undefined, x is not yet constructed
    { }
};

Обратите внимание, что компилятор не необходим для постановки диагноза неопределенного поведения, согласно §1.4 / 1. Хотя я думаю, что все мы согласны с тем, что это было бы неплохо, разработчикам компиляторов просто не о чем беспокоиться.


Чарльз указывает на какую-то лазейку. Если Bar имеет статическое хранилище и если Foo является типом POD, то будет инициализироваться при запуске этого кода. Статические переменные инициализируются нулями перед выполнением другой инициализации.

Это означает, что независимо от Foo, поскольку для его инициализации не требуется конструктор (т. Е. POD), его члены будут инициализироваться нулями. По сути, вы будете копировать инициализированный нулем объект.

В общем, такого кода следует избегать. :)

5 голосов
/ 24 февраля 2010
Bar(): foo(foo) {};

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

class Foo
{
    public:
        Foo()
        {
            std::cout << "Foo()";
        }

        Foo(const Foo& from)
        {
            if(this == &from) std::cout << "special case";
            else std::cout << "other case"; 
        }
};

Но этот особый случай обычно используется для других целей, таких как дешевые копии строк (при использовании строкового класса). Так что не пытайтесь использовать этот особый случай;)

3 голосов
/ 24 февраля 2010

Слегка расширенная версия вашего кода указывает, что нет, foo никогда не инициализируется; вы, казалось бы, имели неопределенное поведение. В этом примере "Foo()" никогда не печатается, указывая, что ни один экземпляр Foo никогда не создается:

#include <iostream>

class Foo {
public:
    Foo() { std::cerr << "Foo()"; }
};

class Bar {
public:
    Foo foo;
    Bar(): foo(foo) {};
};

int main() {
    Bar bar;
}
0 голосов
/ 24 февраля 2010

Разве Foo не использует встроенный конструктор по умолчанию, а список инициализации автоматически вызывает этот конструктор по умолчанию для инициализации объекта?

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