Часто я добавляю к своим объектам C++ метод Empty, чтобы очистить внутреннее состояние, используя код, подобный приведенному ниже.
class Foo
{
private:
int n_;
std::string str_;
public:
Foo() : n_(1234), str_("Hello, world!")
{
}
void Empty()
{
*this = Foo();
}
};
Кажется, это лучше, чем дублирование кода в конструкторе, но мне было интересно, является ли *this = Foo() обычным подходом при очистке объекта? Есть ли проблемы с этим ожиданием, чтобы укусить меня за зад? Есть ли другие лучшие способы достичь такого рода вещей?





Вместо этого я бы позволил конструктору вызвать мою функцию:
class Foo
{
private:
int n_;
std::string str_;
public:
Foo()
{
Reset();
}
void Reset()
{
n_ = 1234;
str_ = "Hello, world!";
}
};
Да, вы без необходимости сначала инициализируете строку как пустую строку, а затем выполняете присваивание, но это намного яснее.
Если Bar не нужно сбрасывать, он будет просто инициализирован при создании: Foo() : Bar(DUMMY) { Reset(); } Если Bar нужно сбрасывать, я бы предположил, что у него также будет функция сброса: void Reset() { Bar::Reset(); ... }. Следовательно, для ясности может произойти некоторая избыточная инициализация членов коллегии адвокатов.
Хорошая точка зрения. Мне нравится этот подход для приложений, не критичных к производительности.
Также подумайте о том, чтобы сделать объекты неизменяемыми, т.е., когда они построены, они не могут быть изменены. Во многих случаях это может спасти вас от непредвиденных побочных эффектов.
рассмотрите возможность использования размещения new:
void Empty() {
this->~Foo();
new (this) Foo();
}
Ваш код вызывает operator =, что может привести к всевозможным побочным эффектам.
РЕДАКТИРОВАТЬ в ответ на комментарии. - Этот код определенно четко определен, стандарт явно разрешает это. Я отправлю этот абзац позже, если найду время. Про delete - конечно. Я имел в виду ~Foo(), это была оплошность. И да, Роб тоже прав; разрушение объекта здесь действительно необходимо для вызова деструктора строки.
О боже. Это похоже на ДЕЙСТВИТЕЛЬНО плохую идею. Вы только что удалили указатель this (т.е. вы только что вернули эту память диспетчеру памяти). Если повезет, быстро рухнешь. В противном случае вы просто передадите поврежденное состояние неизвестному, какая часть вашей программы. Не делай этого.
Вы имели в виду «this-> ~ Foo ()», а не «удалить это».
Даже если не удалить «это», это все равно неправильно. Вы вызываете конструктор в уже инициализированной памяти. Поле std :: string объекта будет перезаписано новым; его деструктор не будет вызван первым.
Спасибо за комментарии. Код был испорчен, сейчас исправил. В свою защиту я сначала неправильно понял вопрос, затем перечитал его и отредактировал свое сообщение. До этого первоначального редактирования код был на самом деле правильным (таким же, как и сейчас!), Моим редактированием я его опроверг.
herb sutter не рекомендует это: gotw.ca/publications/advice97.htm. при этом - да, это является хорошо определено, но, например, выйдет из строя, если вы унаследуете от Foo. herb разбирается в вещах лучше меня и собрал другие задачи.
и да, даже без запуска dtor вручную - если dtor не имеет побочных эффектов, от которых вы зависите, можно пропустить вызов (но тогда это еще хуже :)) - UB там тоже нет. хотя в этом случае - вы бы пропустили вызов строк dtor, где вы не можете гарантировать, что он вызывает не UB.
@litb: Я смутно помню, как мы раньше обсуждали dtor. Вы, конечно, правы насчет производных классов. Однако, поскольку другие участники этой ветки уже указали на эту проблему, я не думаю, что мне нужно обновлять свой ответ, чтобы отразить это.
Конрад, да, твой ответ действительно хорош. просто хотел сказать толпе, как я думаю, в каком состоянии дела, хе-хе :)
Да, это неэффективно с точки зрения производительности (создание другого объекта foo вместо работы на месте), и вас укусит, если вы выделите память в конструкторе с неприятной утечкой памяти.
Чтобы сделать его более безопасным с точки зрения памяти, нужно вызвать this-> delete и this = new foo (), но это будет МЕДЛЕННО.
если вы хотите быть сверхбыстрым, создайте статическое пустое поле объекта и запишите его в Reset.
если вы хотите просто быстро назначать свойства одно за другим.
если вы хотите сохранить разумный стиль без дублирования, вызовите Reset из Ctor, как предлагал Атес Горал, но вы потеряете более быструю конструкцию с параметрами по умолчанию.
Memcpy приводит к неопределенному поведению для типов, не относящихся к POD. А выделение нового объекта и присвоение результата «this» определенно не приведет к тому, что вы намереваетесь сделать.
Потенциальные проблемы? Откуда вы знаете, что * это действительно Фу?
Есть нечто даже более распространенное, чем то, что вы предложили. используя своп.
Обычно вы делаете что-то вроде этого:
T().swap(*this);
поскольку многие стандартные контейнеры (все контейнеры STL?) имеют метод постоянной временной замены, это хороший и простой способ очистить контейнер и убедиться, что его хранилище освобождено.
Точно так же это хороший способ «сжать до размеров контейнера», но с использованием конструктора копирования вместо конструктора по умолчанию.
да, это то, что я хотел опубликовать сейчас, хе-хе. +1
То, что вы делаете с этим методом 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. Он скрывает, что на самом деле происходит с объектом, который он называется, имя тоже не лучший выбор.
Если вызывающему действительно нужен чистый объект - у него не возникнет проблем с его построением.
Собственно моя мысль. Эта «переработка объектов» никоим образом не поможет и может усложнить обслуживание и отладку.
Возможно, «Clear» было бы лучшим названием - многие типы STL имеют «clear» метод.
Объекты stl, у которых есть метод clear, представляют собой классы-контейнеры, которые могут «обмануть» очистку, не освобождая память.
Зачем удалять pfoo, если можно просто "* pfoo = Foo ();"
Хороший звонок Грегу, я добавил его как альтернативу. Спасибо.
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, а DerivedFooFoo не существует или он содержит ошибки (например, если он не определен и оператор по умолчанию не подходит, например, из-за того, что член данных является голым указателем).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? На данный момент он не может получить доступ к закрытым членам.
Конрад, да ладно. я пропустил это :) хорошо, тогда я просто поставлю его как член и вызову из бесплатной функции :) вот как это делает std :: string. Я задавался вопросом, почему это так (с этим дополнительным членом). но этот вопрос с другом, возможно, является ключевым
Но что бы вы сделали, если
Fooунаследовал отBar, у которого нет конструктора по умолчанию, и его нужно будет инициализировать с помощью списка инициализации?