Всем привет! Ко мне через личку обратились товарищи, сказав что, они не хотят комментировать, то что не поняли или поняли не до конца и попросили дать пояснения. На основе присланных вопросов я попытаюсь дать ответы в доступной форме.


Чем полезны иммутабельные данные в С++?


Уменьшение числа побочных эффектов


В С++ есть физическая и логическая константность. Физическая означает, что объект никак не меняется (кроме грубой силы). Логическая означает, что объект внешне неизменен, но может поменять внутреннее состояние.


Например,


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)


  1. ik62
    23.02.2017 03:30
    +1

    По D может оказаться полезной книга Programming in D.


  1. Antervis
    23.02.2017 17:47
    +4

    Примение Immutable, говорит что объект не должен меняться.

    применение const говорит, что объект не должен меняться
    Дополнительный уровень защиты

    Оверхед

    У вас два пункта противоречат друг другу. Либо для передачи immutable по const& будет происходить копия, либо допустим точно такой же хак с const_cast'ом для изменения внутреннего объекта.
    Это вопрос вопрос вкуса, но представляется, что выражение вида
    Immutable<Type **> p(...);
    выглядит понятнее чем
    const Type *const *const p = …;

    Не выглядит. Рядовой c++ программист прекрасно знает что такое const, а шаблон Immutable ему придется изучить. А потом еще часами чесать репу чтобы понять, зачем вообще этот шаблон нужен и используется, кроме как по прихоти автора.


    1. stepik777
      23.02.2017 20:29

      применение const говорит, что объект не должен меняться

      Нет, const в С++ ничего не говорит о том, должен объект меняться или нет, он говорит только о том, что вы не должны его менять. Если в другом месте есть неконстантная ссылка на тот же объект, то он может изменится.


      Например:


      void f(const int& b) {
          std::cout << b; // здесь напечатается одно значение
          g();
          std::cout << b; // а здесь может напечататься уже другое
      }


      1. Antervis
        23.02.2017 22:22
        +4

        в приведенном примере проблема не в том, что b не иммутабелен, а в том, что g() имеет неявный побочный эффект. Если уж говорить о перетаскивании парадигм ФП в с++, то давайте начнем с чистых функций?


      1. 0serg
        25.02.2017 11:28
        +1

        Const примененный непосредственно к обьекту (а не ссылке на обьект) отлично все говорит о его иммутабельности. Существование не -const ссылок — о mutaбельности со стороны кода который ими владеет. И если создавать иммутабельный обьект копированием то достаточно собственно просто создать const-обьект копированием. Причем это можно сделать прямо в аргументах функции, просто из традиционной конструкции const X& амперсанд убираете и все.


  1. DistortNeo
    23.02.2017 19:21

    Вы опять упустили главное, а именно принципиальное отличие const от immutable.


    Указание const в аргументах функции означает, что функция не может изменить значение передаваемой по ссылке переменной. При этом функцию мало волнует внешний код.


    А передача immutable означает, что ни функция, ни внешний код не могут изменить значение переменной в процессе выполнения функции. Здесь immutable является контрактом.


    Всё остальное (приведение типов, хитрое представление в памяти) — ерунда. Забудьте про const_cast — это тоже злобный хак, которого стоит избегать. Единственное его предназначение — создание костыля для работы с какой-нибудь старой библиотекой. В хорошем коде const_cast должен отсутствовать.