Я хотел бы предложить, что если кто-то хочет эффективно реализовать копирование при записи (для строк или чего-либо еще), следует определить тип оболочки, который будет вести себя как изменяемая строка и который будет содержать как обнуляемую ссылку на изменяемый строка (никакой другой ссылки на этот элемент никогда не будет) и пустая ссылка на «неизменяемую» строку (ссылки на которые никогда не будут существовать вне вещей, которые не будут пытаться изменить его). Обертки всегда будут создаваться с хотя бы одной из этих ссылок, отличных от NULL; как только для ссылки на изменяемый элемент будет установлено ненулевое значение (во время или после создания), он всегда будет ссылаться на одну и ту же цель. Каждый раз, когда обе ссылки не равны NULL, ссылка на неизменяемый элемент будет указывать на копию элемента, которая была сделана через некоторое время после самой последней завершенной мутации (во время мутации ссылка на неизменяемый элемент может содержать или не содержать ссылку). до значения перед мутацией).
Чтобы прочитать объект, проверьте, не является ли ссылка "mutable-item" ненулевой. Если так, используйте это. В противном случае проверьте, не является ли ссылка «immutable-item» ненулевой. Если так, используйте это. В противном случае используйте ссылку «изменяемый элемент» (которая к настоящему времени будет не нулевой).
Чтобы изменить объект, проверьте, не является ли ссылка "mutable-item" ненулевой. Если нет, скопируйте цель ссылки «неизменяемый элемент» и CompareExchange ссылку на новый объект в ссылку «изменяемый элемент». Затем измените цель ссылки «изменяемый элемент» и аннулируйте ссылку «неизменяемый элемент».
Чтобы клонировать объект, если ожидается, что клон будет снова клонирован до его мутации, извлеките значение ссылки "immutable-item". Если это значение равно null, сделайте копию цели «изменяемый элемент» и CompareExchange ссылкой на этот новый объект в ссылку на неизменяемый элемент. Затем создайте новую оболочку, чья ссылка «mutable-item» равна нулю, а чья ссылка «immutable-item» является либо полученным значением (если оно не было нулевым), либо новым элементом (если он был).
Чтобы клонировать объект, если ожидается, что клон будет мутирован до его клонирования, извлеките значение ссылки "immutable-item". Если ноль, получить ссылку "mutable-item". Скопируйте цель для любой ссылки, которая была получена, и создайте новую оболочку, чья ссылка "mutable-item" указывает на новую копию, а чья ссылка "immutable-item" равна нулю.
Два метода клонирования будут семантически идентичны, но выбор неправильного метода для данной ситуации приведет к дополнительной операции копирования. Если кто-то последовательно выбирает правильную операцию копирования, он получит большую часть преимуществ «агрессивного» подхода «копирование при записи», но с гораздо меньшими накладными расходами. Каждый объект хранения данных (например, строка) будет либо непостоянным изменяемым, либо общим неизменяемым, и ни один объект никогда не будет переключаться между этими состояниями. Следовательно, при желании можно было бы устранить все «издержки на многопоточность / синхронизацию» (заменив операции CompareExchange прямыми хранилищами) при условии, что ни один объект-оболочка не используется более чем в одном потоке одновременно. Два объекта-обертки могут содержать ссылки на один и тот же неизменный держатель данных, но они могут не замечать существование друг друга.
Обратите внимание, что при использовании этого подхода может потребоваться еще несколько операций копирования, чем при использовании "агрессивного" подхода. Например, если новая оболочка создается с новой строкой, и эта оболочка видоизменяется и копируется шесть раз, исходная оболочка будет содержать ссылки на исходный держатель строки и неизменную, содержащую копию данных. Шесть скопированных оболочек будут содержать ссылку на неизменяемую строку (всего две строки, хотя, если исходная строка никогда не была видоизменена после того, как была сделана копия, агрессивная реализация могла бы обойтись одной). Если оригинальная оболочка была видоизменена вместе с пятью из шести копий, то все ссылки на неизменяемую строку, кроме одной, станут недействительными. В этот момент, если шестая копия оболочки была видоизменена, агрессивная реализация копирования при записи может понять, что она содержит единственную ссылку на свою строку, и, таким образом, решить, что копия не нужна. Реализация, которую я описываю, однако, создаст новую изменяемую копию и откажется от неизменной. Несмотря на то, что существуют некоторые дополнительные операции копирования, сокращение накладных расходов в большинстве случаев должно более чем компенсировать затраты. Если большинство создаваемых логических копий никогда не видоизменяются, этот подход может быть более эффективным, чем всегда делать копии строк.