Ответ 1
Предварительные/последующие условия назначения контейнера, указанные в стандарте (с указанием последней версии):
[tab:container.req]
r = a
Ensures: r == a.
Это позволяет, но не требует проверки самостоятельного назначения.
Вопрос о самоназначении. Например, копирование вектора в себя:
std::vector<std::string> vec(5, "hello");
vec = vec;
Должен ли приведенный выше код выполнять 5 операций присваивания строк самим себе или просто ничего не делать? Я имею в виду, действительна ли следующая проверка:
std::vector operator=(const std::vector &rhs)
{
if (this == &rhs)
{ return *this; }
...
}
Я работаю над собственной реализацией класса std::variant
(просто для удовольствия) и мне интересно, стоит ли мне добавлять проверку самоназначения в начало оператора присваивания или мне просто нужно скопировать содержащийся элемент в себя?
Я понимаю, что в целом это не имеет значения. Вы не должны создавать класс, который использует факт копирования в себя. Но мне интересно, если стандарт говорит что-нибудь об этом.
Предварительные/последующие условия назначения контейнера, указанные в стандарте (с указанием последней версии):
[tab:container.req]
r = a
Ensures: r == a.
Это позволяет, но не требует проверки самостоятельного назначения.
Заинтересован ли мне добавить проверку самоназначения в начало оператора присваивания или мне просто нужно скопировать содержащийся элемент в себя?
C++ Core Guidelines рекомендует не выполнять проверку самостоятельного назначения в классе пользователя, если все его члены безопасны для самостоятельного назначения:
Исполнение (Простое) операторы присваивания не должны содержать шаблон
if (this == &a) return *this;
???
Это из соображений эффективности. Самостоятельные задания вряд ли произойдут на практике. Они редки, и поэтому лучше избегать проверки самоназначения в каждой операции. Проверка самопредставления, вероятно, ускоряет выполнение кода в случае самопредставления (очень редко) и замедляет его во всех других случаях (чаще).
Представьте, что вы назначаете миллион элементов. В каждой операции присваивания выполняется проверка самоназначения. И, скорее всего, это сделано даром, потому что ни одно из заданий на самом деле не является самостоятельным заданием. И поэтому мы делаем миллион бесполезных проверок.
Если мы пропустим проверку самоназначения, то ничего плохого не произойдет, за исключением того, что если самопредставление действительно произойдет, то мы делаем бесполезные самоназначения всех членов (что иногда медленнее, чем однократная проверка самоназначения в начале присвоения). оператор). Но если ваш код выполняет миллион самостоятельных назначений, это является причиной для пересмотра вашего алгоритма, а не для выполнения проверки самостоятельного назначения во всех операциях.
Тем не менее, проверка самоназначения по-прежнему должна использоваться для классов, которые по умолчанию не являются безопасными при самоопределении Примером является std::vector
. Вектор, который копируется, сначала должен удалить существующие элементы. Но если вектор назначения и исходный вектор - это один и тот же объект, то удаляя элементы в целевом векторе, мы также удаляем их в исходном векторе. И поэтому их невозможно будет скопировать после удаления. Вот почему libstd C++ выполняет проверку самоназначения для std::vector
(хотя можно реализовать std::vector
без проверки самоназначения).
Но он не делает это для std::variant
, например. Если вы копируете вариант в себя, то содержащееся в нем значение будет скопировано в себя. Смотрите живой пример. Поскольку его копирование в себя является безопасным для присваивания (при условии, что содержащееся в нем значение является безопасным для присваивания).
Таким образом, libstd C++ выполняет проверку самоназначения для std::vector
(для обеспечения безопасности самоназначения), а не для std::variant
(для эффективности).
[...] если я должен добавить эту проверку в начало оператора присваивания [...]?
Вы должны делать это независимо от того, std::vector
или другие контейнеры STL делают это за вас. Представьте себе пользователя, который работает с вашей библиотекой и выполняет x = x
вне области действия контейнеров STL.
Теперь о требованиях к контейнеру STL - я полагаю, что в стандарте не указывается, требуется ли назначение для выполнения проверки на то, что оно является самостоятельным назначением (прошло большую часть раздела Containers library
). Это дает место для оптимизаций компилятора, и я считаю, что достойный компилятор должен выполнять такие проверки.
Проверка this == &rhs
на самом деле является довольно хорошо известной идиомой и помогает быть уверенным, что вы ничего не сломаете, гарантируя, что lhs
и rhs
- это разные объекты. Таким образом, это действительно и действительно поощряется.
Я не знаю, требуются ли контейнеры STL для проверки.