Очистка объекта C++

Часто я добавляю к своим объектам C++ метод Empty, чтобы очистить внутреннее состояние, используя код, подобный приведенному ниже.

class Foo
{
private:
    int n_;
    std::string str_;
public:
    Foo() : n_(1234), str_("Hello, world!")
    {
    }

    void Empty()
    {
        *this = Foo();
    }
};

Кажется, это лучше, чем дублирование кода в конструкторе, но мне было интересно, является ли *this = Foo() обычным подходом при очистке объекта? Есть ли проблемы с этим ожиданием, чтобы укусить меня за зад? Есть ли другие лучшие способы достичь такого рода вещей?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
0
6 135
10
Перейти к ответу Данный вопрос помечен как решенный

Ответы 10

Ответ принят как подходящий

Вместо этого я бы позволил конструктору вызвать мою функцию:

class Foo
{
private:
    int n_;
    std::string str_;
public:
    Foo()
    {
        Reset();
    }

    void Reset()
    {
        n_ = 1234;
        str_ = "Hello, world!";
    }
};

Да, вы без необходимости сначала инициализируете строку как пустую строку, а затем выполняете присваивание, но это намного яснее.

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

paracycle 02.01.2010 23:39

Если Bar не нужно сбрасывать, он будет просто инициализирован при создании: Foo() : Bar(DUMMY) { Reset(); } Если Bar нужно сбрасывать, я бы предположил, что у него также будет функция сброса: void Reset() { Bar::Reset(); ... }. Следовательно, для ясности может произойти некоторая избыточная инициализация членов коллегии адвокатов.

Ates Goral 05.01.2010 02:53

Хорошая точка зрения. Мне нравится этот подход для приложений, не критичных к производительности.

paracycle 06.01.2010 13:43

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

рассмотрите возможность использования размещения new:

void Empty() {
    this->~Foo();
    new (this) Foo();
}

Ваш код вызывает operator =, что может привести к всевозможным побочным эффектам.

РЕДАКТИРОВАТЬ в ответ на комментарии. - Этот код определенно четко определен, стандарт явно разрешает это. Я отправлю этот абзац позже, если найду время. Про delete - конечно. Я имел в виду ~Foo(), это была оплошность. И да, Роб тоже прав; разрушение объекта здесь действительно необходимо для вызова деструктора строки.

О боже. Это похоже на ДЕЙСТВИТЕЛЬНО плохую идею. Вы только что удалили указатель this (т.е. вы только что вернули эту память диспетчеру памяти). Если повезет, быстро рухнешь. В противном случае вы просто передадите поврежденное состояние неизвестному, какая часть вашей программы. Не делай этого.

Michael Kohne 12.01.2009 21:01

Вы имели в виду «this-> ~ Foo ()», а не «удалить это».

Greg Rogers 12.01.2009 21:01

Даже если не удалить «это», это все равно неправильно. Вы вызываете конструктор в уже инициализированной памяти. Поле std :: string объекта будет перезаписано новым; его деструктор не будет вызван первым.

Rob Kennedy 12.01.2009 21:04

Спасибо за комментарии. Код был испорчен, сейчас исправил. В свою защиту я сначала неправильно понял вопрос, затем перечитал его и отредактировал свое сообщение. До этого первоначального редактирования код был на самом деле правильным (таким же, как и сейчас!), Моим редактированием я его опроверг.

Konrad Rudolph 12.01.2009 21:33

herb sutter не рекомендует это: gotw.ca/publications/advice97.htm. при этом - да, это является хорошо определено, но, например, выйдет из строя, если вы унаследуете от Foo. herb разбирается в вещах лучше меня и собрал другие задачи.

Johannes Schaub - litb 13.01.2009 16:09

и да, даже без запуска dtor вручную - если dtor не имеет побочных эффектов, от которых вы зависите, можно пропустить вызов (но тогда это еще хуже :)) - UB там тоже нет. хотя в этом случае - вы бы пропустили вызов строк dtor, где вы не можете гарантировать, что он вызывает не UB.

Johannes Schaub - litb 13.01.2009 16:53

@litb: Я смутно помню, как мы раньше обсуждали dtor. Вы, конечно, правы насчет производных классов. Однако, поскольку другие участники этой ветки уже указали на эту проблему, я не думаю, что мне нужно обновлять свой ответ, чтобы отразить это.

Konrad Rudolph 14.01.2009 10:57

Конрад, да, твой ответ действительно хорош. просто хотел сказать толпе, как я думаю, в каком состоянии дела, хе-хе :)

Johannes Schaub - litb 14.01.2009 11:29

Да, это неэффективно с точки зрения производительности (создание другого объекта foo вместо работы на месте), и вас укусит, если вы выделите память в конструкторе с неприятной утечкой памяти.

Чтобы сделать его более безопасным с точки зрения памяти, нужно вызвать this-> delete и this = new foo (), но это будет МЕДЛЕННО.

если вы хотите быть сверхбыстрым, создайте статическое пустое поле объекта и запишите его в Reset.

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

если вы хотите сохранить разумный стиль без дублирования, вызовите Reset из Ctor, как предлагал Атес Горал, но вы потеряете более быструю конструкцию с параметрами по умолчанию.

Memcpy приводит к неопределенному поведению для типов, не относящихся к POD. А выделение нового объекта и присвоение результата «this» определенно не приведет к тому, что вы намереваетесь сделать.

Rob Kennedy 12.01.2009 21:07

Потенциальные проблемы? Откуда вы знаете, что * это действительно Фу?

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

Обычно вы делаете что-то вроде этого:

T().swap(*this);

поскольку многие стандартные контейнеры (все контейнеры STL?) имеют метод постоянной временной замены, это хороший и простой способ очистить контейнер и убедиться, что его хранилище освобождено.

Точно так же это хороший способ «сжать до размеров контейнера», но с использованием конструктора копирования вместо конструктора по умолчанию.

да, это то, что я хотел опубликовать сейчас, хе-хе. +1

Johannes Schaub - litb 13.01.2009 16:04

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

Лично я бы удалил метод Empty и заменил все его использования следующим образом:

// let's say, that you have variables foo and pfoo - they are properly initialized.
Foo foo, *pfoo;

// replace line "foo.Empty()" with:
foo = Foo();

// replace line "pfoo->Empty()" with:
delete pfoo;
pfoo = new Foo();
// or
*pfoo = Foo();

Я действительно не вижу преимуществ в использовании этого метода Empty. Он скрывает, что на самом деле происходит с объектом, который он называется, имя тоже не лучший выбор.

Если вызывающему действительно нужен чистый объект - у него не возникнет проблем с его построением.

Собственно моя мысль. Эта «переработка объектов» никоим образом не поможет и может усложнить обслуживание и отладку.

Nemanja Trifunovic 12.01.2009 21:41

Возможно, «Clear» было бы лучшим названием - многие типы STL имеют «clear» метод.

Rob 12.01.2009 21:56

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

Eclipse 12.01.2009 22:51

Зачем удалять pfoo, если можно просто "* pfoo = Foo ();"

Greg Rogers 13.01.2009 00:47

Хороший звонок Грегу, я добавил его как альтернативу. Спасибо.

Paulius 13.01.2009 00:51

This seems to be better than duplicating code in the constructor, but I wondered if *this = Foo() is a common approach when wanting to clear an object?

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

Самым распространенным типом сброса находятся будут контейнеры ... но вы бы сейчас не писали свои собственные классы контейнеров, не так ли?

Are there any problems with this waiting to bite me on the backside?

Да:

  • Если это на самом деле не Foo, а DerivedFoo
  • Если оператор присваивания Foo не существует или он содержит ошибки (например, если он не определен и оператор по умолчанию не подходит, например, из-за того, что член данных является голым указателем).

Are there any other better ways to achieve this sort of thing?

Да, возможно, бесплатная функция была бы лучше (позволила бы избежать обеих вышеуказанных проблем):

template<class T> void reconstruct(T* p)
{
    p->~T();
    new (p) T();
}

Это может быть потенциальным источником утечки памяти, если у вас есть динамически выделяемая память в конструкторе.

Вот как я это делаю:

class Foo {
private:
    int n_;
    std::string str_;
public:
    Foo() : n_(1234), str_("Hello, world!")
    {
    }

    void Empty()
    {
        Foo f;
        swap(f);
    }

    void swap(Foo & other) {
        std::swap(n_, other.n_);
        swap(str_, other.str_);
    }
};

void swap(Foo & one, Foo & other) {
    one.swap(other);
}

Поместите функцию подкачки в то же пространство имен, что и класс Foo. Поиск, зависящий от аргумента, находит его, когда пользователи выполняют вызов, чтобы поменять местами два Foo. Вы также можете реализовать operator=, используя функцию подкачки.

Как насчет того, чтобы сделать swapfriend из Foo? На данный момент он не может получить доступ к закрытым членам.

Konrad Rudolph 14.01.2009 10:59

Конрад, да ладно. я пропустил это :) хорошо, тогда я просто поставлю его как член и вызову из бесплатной функции :) вот как это делает std :: string. Я задавался вопросом, почему это так (с этим дополнительным членом). но этот вопрос с другом, возможно, является ключевым

Johannes Schaub - litb 14.01.2009 11:23

Другие вопросы по теме