Всем привет! Ко мне через личку обратились товарищи, сказав что, они не хотят комментировать, то что не поняли или поняли не до конца и попросили дать пояснения. На основе присланных вопросов я попытаюсь дать ответы в доступной форме.
Чем полезны иммутабельные данные в С++?
Уменьшение числа побочных эффектов
В С++ есть физическая и логическая константность. Физическая означает, что объект никак не меняется (кроме грубой силы). Логическая означает, что объект внешне неизменен, но может поменять внутреннее состояние.
Например,
class Test {
public:
void foo() const
{
// меняет m_object
}
private:
mutable Object m_object;
};
const Test test(...);
test.foo();
Объект изменился. Где это можно встретить:
- кэширование;
- разделяемые данные;
- внутренние счетчики (например, количество вызов функции для данного объекта) и т. д.
Есть ситуации, когда нам такие сюрпризы не нужны. Создаем изначально иммутабельный объект:
Immutable<Test> test(...);
пользуемся
test.foo();
или пока нету оператора .:
test().foo();
Объект test остался неизменным. Где это нужно:
1) в параллельном программировании;
2) при работе с «вещью в себе» и мы не хотим сюрпризов.
Дополнительный уровень защиты
Начну с примера:
Есть константный объект
Type x(...);
f(x);
и функция
void f(const Test &a)
{
...
const_cast<Test&>(a) = value;
...
}
Это может быть нарушение, может быть ошибка, но компилятор даже не пикнет.
Рассмотрим другую ситуацию. Есть иммутабельный объект:
Immutable<Type> x(...);
f(x);
и
void f(const Immutable<Test> &a)
{
const_cast<Immutable<Test> &>(a) = value;
}
Компилятор будет ругаться.
Ломануть можно только с помощью reinterpret_cast, с которым можно обломаться, если защищаемый объект хранить нестандартным образом (например, в сжатом/зашифрованном/сериализованном виде) .
Тот же эффект и при
void f(Immutable<Test> &a)
{
const_cast<Immutable<Test> &>(a) = value;
}
Примение Immutable, говорит что объект не должен меняться.
Применение в специфических областях
Не буду говорить про ФП, агентно-ориентированное программирование, про модель акторов и классной доски, а скажу про параллельное программирование (модель акторов и классной доски там активно используются).
И скажу сначала в неформальном, несколько утрированном виде.
Предположим на git (или на другой системе контроля версий) есть проект. Есть N программистов. Каждый должен сделать свою версию проекта. Что делается:
- ограничивается доступ к проекту, чтобы не испортили;
- каждый делаем fork;
- работает с ним;
- закончив несет начальнику со словами «я сделяль».
Итог, исходный проект цел, начальник может выбирать из fork-ов.
Перенося это в параллельное программирование:
- есть некие исходные данные (про которые известно по-минимуму);
- есть потоки, которые должны обработать данные;
- данные должны обрабатываться в разных потоках без проблем.
Контрактное программирование
Иммутабельность говорит, что при работе с ними нету сюрпризов.
Эстетичность и самодокументированность
Это вопрос вопрос вкуса, но представляется, что выражение вида
Immutable<Type **> p(...);
выглядит понятнее чем
const Type *const *const p = …;
т.к, иммутабельность сразу говорит что объект никак не должен меняться, что константность используется как для адреса, так и для содержимого.
Кривизна или прямизна реализации
Это тоже вопрос вкуса. Но реализация сделана в соответствии с принципами ФП, при этом оставляя возможности для эффективной работы в стиле С++. См. «Оверхед». Вы можете пользоваться как универсальным классом Immutable, так и вспомогательными классы immutable::array, immutable::pointer и т. д. Эти вспомогательные классы не связаны друг с другом.
Оверхед
Вы можете делать объект изначально иммутабельным и передавать его по ссылке. Копии можете делать при необходимости. Никто не заставляет вас пользоваться тем, что не используете.
Если Вы хотите работать с внутренними данными, то можете получить ОДИН раз копию внутренних данных и работать с ними. Для этого есть функция value.
Здесь можно возразить, что получив доступ к внутренним данным, можно испортить иммутабельность. Во-первых, value возвращает константные данные, а во-вторых это уже сознательные действия программиста. Многие библиотечные классы возвращают нативное представление объектов, играя с которым можно нагадить.
Но это, еще раз, программист получает внутренние данные, причем в данном случае константные, и это СОЗНАТЕЛЬНЫЕ действия программиста.
Необязательно, но полезно
Для дальнейшего изучения рекомендую:
Мэтью Уилсон «Практический подход к решению проблем программирования С++»:
автор книги предлагает реализацию, того чего ему не хватает в С++ (до С++11, но часть вопросов актуальна и ныне), например property и т. д.
Андрей Александреску «Язык программирования D», стиль у него своеобразный, но интересно почитать, как в ЯП встроено контрактное программирование.
Комментарии (6)
Antervis
23.02.2017 17:47+4Примение Immutable, говорит что объект не должен меняться.
применение const говорит, что объект не должен меняться
Дополнительный уровень защиты
…
Оверхед
У вас два пункта противоречат друг другу. Либо для передачи immutable по const& будет происходить копия, либо допустим точно такой же хак с const_cast'ом для изменения внутреннего объекта.
Это вопрос вопрос вкуса, но представляется, что выражение вида
Immutable<Type **> p(...);
выглядит понятнее чем
const Type *const *const p = …;
Не выглядит. Рядовой c++ программист прекрасно знает что такое const, а шаблон Immutable ему придется изучить. А потом еще часами чесать репу чтобы понять, зачем вообще этот шаблон нужен и используется, кроме как по прихоти автора.stepik777
23.02.2017 20:29применение const говорит, что объект не должен меняться
Нет, const в С++ ничего не говорит о том, должен объект меняться или нет, он говорит только о том, что вы не должны его менять. Если в другом месте есть неконстантная ссылка на тот же объект, то он может изменится.
Например:
void f(const int& b) { std::cout << b; // здесь напечатается одно значение g(); std::cout << b; // а здесь может напечататься уже другое }
Antervis
23.02.2017 22:22+4в приведенном примере проблема не в том, что b не иммутабелен, а в том, что g() имеет неявный побочный эффект. Если уж говорить о перетаскивании парадигм ФП в с++, то давайте начнем с чистых функций?
0serg
25.02.2017 11:28+1Const примененный непосредственно к обьекту (а не ссылке на обьект) отлично все говорит о его иммутабельности. Существование не -const ссылок — о mutaбельности со стороны кода который ими владеет. И если создавать иммутабельный обьект копированием то достаточно собственно просто создать const-обьект копированием. Причем это можно сделать прямо в аргументах функции, просто из традиционной конструкции const X& амперсанд убираете и все.
DistortNeo
23.02.2017 19:21Вы опять упустили главное, а именно принципиальное отличие
const
отimmutable
.
Указание
const
в аргументах функции означает, что функция не может изменить значение передаваемой по ссылке переменной. При этом функцию мало волнует внешний код.
А передача
immutable
означает, что ни функция, ни внешний код не могут изменить значение переменной в процессе выполнения функции. Здесьimmutable
является контрактом.
Всё остальное (приведение типов, хитрое представление в памяти) — ерунда. Забудьте про
const_cast
— это тоже злобный хак, которого стоит избегать. Единственное его предназначение — создание костыля для работы с какой-нибудь старой библиотекой. В хорошем кодеconst_cast
должен отсутствовать.
ik62
По D может оказаться полезной книга Programming in D.