Забава конструкторов C++ - создающий Foo с копией себя

Я имею:

class Foo;

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

Bar bar;

На данном этапе

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

[Этот вопрос явился результатом багги касательно - считаемая реализация указателя; я, возможно, поклялся, что удостоверился, что каждый указатель указывал на что-то непустое; но я закончил с указателем, который указал на что-то ПУСТОЕ.]

8
задан GManNickG 24 February 2010 в 20:26
поделиться

4 ответа

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

В вашем случае, вы копируете-конструируете из неконструированного объекта. Это приводит к неопределенному поведению, согласно §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), ее члены будут инициализированы с нуля. По сути, вы копируете объект с нулевой инициализацией.

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

.
12
ответ дан 5 December 2019 в 08:23
поделиться

Это заманчиво, но в конечном счете, это зависит от ситуации.

  • Если вы хотите, чтобы программа потерпела крах, а не рисковала оказаться в плохом или странном состоянии, то, во что бы то ни стало, бросьте исключение.
  • Если вам действительно все равно (хорошенько подумайте об этом) и только нужно отметить исключение, то обертывание блоками try/catch имеет идеальный смысл.
  • Если вы хотите дать всем обработчикам событий шанс запустить перед сбоем (например, некоторые обработчики высвобождают ресурсы), то это требует немного больше усилий...
-121--2149277-

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

Вы также можете (если вы еще не сделали этого) взглянуть на эти посты от Дугласа Крокфорда на то, что он думает о классическом наследстве в javascript.

http://www.crockford.com/javascript/inheritance.html

http://javascript.crockford.com/prototypal.html

-121--3181186-
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"; 
        }
};

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

5
ответ дан 5 December 2019 в 08:23
поделиться

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

#include <iostream>

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

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

int main() {
    Bar bar;
}
3
ответ дан 5 December 2019 в 08:23
поделиться

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

0
ответ дан 5 December 2019 в 08:23
поделиться
Другие вопросы по тегам:

Похожие вопросы: