Цепочка методов C вызывает двойной вызов деструктора

#c #destructor #method-chaining

#c #деструктор #метод-цепочка

Вопрос:

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

Код приведен ниже:

 class Foo {
public:
    Foo()   { cout << "-- constructor " << this << endl; }
    ~Foo () { cout << "-- destructor  " << this << endl; };

    Fooamp; bar() {
        cout << "---- bar call  " << this << endl;
        return *this;
    }
};

int main() {
    cout << "starting test 1" << endl;
    {
        Foo f = Foo();
    }
    cout << "ending test 1"   << endl << endl;
    cout << "starting test 2" << endl;
    {
        Foo f = Foo().bar();
    }
    cout << "ending test 2"   << endl;
    return 0;
}
 

Результат этого приложения следующий:

 starting test 1
-- constructor 0x7ffd008e005f
-- destructor  0x7ffd008e005f
ending test 1

starting test 2
-- constructor 0x7ffd008e005f
---- bar call  0x7ffd008e005f
-- destructor  0x7ffd008e005f
-- destructor  0x7ffd008e005f
ending test 2
 

Является ли это стандартным поведением (если да, то почему?) Или я допустил какую-то ошибку? Могу ли я предотвратить это?

Комментарии:

1. Добавьте конструктор копирования и оператор присваивания, чтобы увидеть, что происходит

2. Копия, упомянутая во всех приведенных ниже ответах, исключается в первом случае. Если вы скомпилируете с g -fno-elide-constructors помощью, вы увидите, что вызывается 2 деструктора.

3. Что ж, это довольно интересно. Спасибо!

4. Это не проблема, но не используйте std::endl , если вам не нужны дополнительные вещи, которые он делает. 'n' завершает строку.

Ответ №1:

Foo f = Foo().bar(); также вызывает конструктор копирования Foo , который в настоящее время генерируется компилятором и поэтому ничего не выводит на консоль. Вот почему похоже, что вы вызываете больше деструкторов, чем конструкторов.

Вы могли бы написать const Fooamp; f = Foo().bar(); , чтобы избежать копирования. Использование a const также продлевает срок службы анонимного временного, что приятно.

Ответ №2:

Обратите внимание, что в Foo строке задействованы два объекта типа:

 Foo f = Foo().bar();
    ^----------------this one
        ^------------and this one
 

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

Ответ №3:

Существует еще один конструктор, который вы не реализовали самостоятельно. Конструктор копирования.

Ваш звонок Foo f = Foo().bar(); может быть записан как Foo tmp = Foo(); Foo f = tmp.bar();

Только создание экземпляра вашего tmp объекта вызовет ваш конструктор. Вызываемый конструктор f — это автоматически сгенерированный конструктор-копия.

Это должно дать вам лучший результат:

 #include <iostream>
using std::cout;
using std::endl;

class Foo {
public:
    Foo() { cout << "-- constructor " << this << endl; }
    Foo(const Fooamp; f) { cout << "-- copy-constructor " << this << endl; }
    ~Foo() { cout << "-- destructor  " << this << endl; };

    Fooamp; bar() {
        cout << "---- bar call  " << this << endl;
        return *this;
    }
};

int main() {
    cout << "starting test 1" << endl;
    {
        Foo f = Foo();
    }
    cout << "ending test 1" << endl << endl;
    cout << "starting test 2" << endl;
    {
        Foo f = Foo().bar();
    }
    cout << "ending test 2" << endl;
    return 0;
}
 

 starting test 1
-- constructor 000000EC09CFF944
-- destructor  000000EC09CFF944
ending test 1

starting test 2
-- constructor 000000EC09CFFA44
---- bar call  000000EC09CFFA44
-- copy-constructor 000000EC09CFF964
-- destructor  000000EC09CFFA44
-- destructor  000000EC09CFF964
ending test 2
 

Ответ №4:

Это ожидаемое поведение. Даже если вы возвращаете ссылку из bar , вы ее не используете.

 Foo f = Foo().bar();
 

фиксирует возвращаемое значение по значению, поэтому вы создаете копию. Это означает, что Foo from Foo() уничтожается в конце выражения, а его копия, которую вы создаете, f уничтожается при выходе из области видимости.