Pull to refresh

Иммутабельные данные в С++. Часть 2

Reading time 3 min
Views 9.1K

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


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


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


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


Например,


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», стиль у него своеобразный, но интересно почитать, как в ЯП встроено контрактное программирование.

Tags:
Hubs:
+6
Comments 6
Comments Comments 6

Articles