Подтвердить что ты не робот

Логический const в D

D имеет два типа константы: неизменяемые переменные - это те, которые были объявлены неизменяемыми и всегда будут неизменными, а константные переменные - это просто версии объекта только для чтения.

Логическая константа - это когда функция помечена как const, но допускает доступ на запись к одной или нескольким переменным-членам. Типичное использование этого метода - для ленивой оценки, например. (в С++)

struct Matrix
{
  double determinant() const
  {
    if ( m_dirty )
    {
      m_determinant = /* expensive calculation */;
      m_dirty = false;
    }
    return m_determinant;
  }

  void set(int i, int j, double x) { m_dirty = true; ...; }

  mutable bool m_dirty;
  mutable double m_determinant;
};

Здесь determinant() есть const, но все еще может изменить m_dirty и m_determinant, потому что они помечены как mutable.

D const (FAQ) говорит, что D2 не поддерживает логическую константу из-за слабой гарантии, которую она предоставляет, что является препятствием к написанию параллельных программ и затрудняет определенные оптимизации.

Я полностью понимаю беспокойство, но что, если нам нужна логическая константа?

Рассмотрим вышеприведенный случай с классом Matrix, но без кеширования (и любой необходимости для логической константы). Также представьте, что этот класс используется по всей моей кодовой базе и в основном доступен через ссылки const.

Теперь рассмотрим, что профилирование показало, что функция determinant() является узким местом в коде, и, кроме того, к ней обычно обращаются неоднократно, причем ее значение редко изменяется, т.е. кэширование, как указано выше, будет идеальной оптимизацией.

Как я могу сделать это без логического const? По всем моим кодовым базам изменение константных ссылок на неконстантные ссылки не является вариантом (по понятным причинам).

Какие параметры у меня есть (если есть)?

4b9b3361

Ответ 1

Я думаю, что было бы целесообразно опубликовать основные выводы недавнего потока по этой теме в группе новостей D здесь, так что те, кто не отслеживает этот список, могут получить соответствующий ответ.

D const не является логическим const. Он транзитивен и полностью const. Язык технически не поддерживает логическую константу. Язык не определяет способ мутации объекта const.

И на самом деле, у С++ тоже нет логического const. Используя mutable и исключающую константу, вы можете полностью обходить const, так что технически говорящий const не гарантирует ничего, кроме того, что вы не вызываете какие-либо неконстантные функции в переменной const. Тот факт, что const-функции на самом деле const и не винт с вашими переменными, полностью скрепляется условно. В настоящее время большинство программистов не избегают отбрасывания константы влево и вправо и делают все изменчивым, так что на практике это весьма полезно, но это не только может быть полностью обойдено, но и в плане lanuage дает вам определенные средства для этого, В С++ mutable и отбрасывание const хорошо определены и поддерживаются языком.

D не делает этого. D const фактически const. Отбрасывание const на переменную, а затем ее изменение undefined. Нет изменчивого. D const имеет реальные гарантии (пока вы ничего не делаете undefined, например, отбрасываете const на что-то, а затем мутируете его). Это важно не только потому, что компилятор гарантирует, что D намного сильнее, чем для С++, но поскольку неизменяемые переменные не могут быть изменены каким-либо образом формы или формы. Они могут быть в памяти только для чтения, и кто знает, какие ужасные вещи произойдут, если вы попытаетесь отбросить неизменность и изменить такую ​​переменную (segfault, вероятно, будет самой приятной вещью, которая может произойти). И так как константная переменная действительно может ссылаться на неизменяемые данные, отбрасывание const для изменения переменной или допускать изменение константных переменных было бы, по меньшей мере, плохим. Таким образом, язык не позволяет этого.

Теперь, когда указывает BCS, D является прагматичным языком. Вы можете отбросить const, после чего вы можете изменить переменную. Так, например, у вас может быть переменная, которая использовалась для кэширования возвращаемого значения функции const (предположительно, если этот кеш был недействителен, если состояние объекта изменилось) и отбрасывает const, чтобы изменить его. Пока эта переменная не является фактически неизменной, она будет работать. Однако это поведение undefined. Как только вы это сделаете, вы по своему усмотрению. Вы обходите систему типов и гарантии компилятора. Вы тот, кто несет ответственность за то, чтобы вы не делали этого на неизменяемом объекте или иным образом не испортили то, что компилятор обычно гарантирует. Итак, если вам нужно это сделать, вы можете, но вы выходите на Дикий Запад, и это зависит от вас, чтобы вы не мутировали то, что вам не нужно.

Учитывая, что отбрасывание const будет работать до тех пор, пока переменная на самом деле не ссылается на неизменяемые данные, возможно создать шаблон mutable, чтобы получить то, что mutable дает вам на С++ (так, сделаю для вас непоколебимость). he_the_great дает пример такого шаблона в своем ответе. Но использование такого шаблона по-прежнему undefined. Использование его на объекте, который на самом деле неизменен, вызовет проблемы. Вы, программист, должны убедиться, что он правильно используется.

Итак, D делает технически возможным иметь логический const, отбрасывая const, но для этого вам нужно выйти за пределы того, что гарантирует компилятор, минуя систему типов, и вы должны убедиться, что вы 'т злоупотреблять им и мутировать переменные, которые не должны/не могут быть мутированы, или ваш код будет иметь проблемы - segfaults, возможно, наименее среди них.

EDIT:Я забыл упомянуть одно предлагаемое решение, которое не нарушает систему типов. До тех пор, пока вы готовы отказаться от чистоты, вы можете использовать глобальную переменную некоторого разнообразия (будь то в области модуля, переменной класса или переменной struct) для хранения ваших кешированных значений. Функция const может свободно использовать и мутировать глобальные переменные, поэтому ее можно использовать вместо отсутствующего mutable. Это означает, однако, что функция не может быть чистой, что также может быть большой проблемой. Это, однако, способ иметь функцию const, которая по-прежнему может мутировать данные, которые ей нужны, без нарушения системы типов.

Ответ 2

Я не дотрагивался до D2 в возрасте, поэтому вы можете дважды проверить, что я говорю.:)

Я не уверен, что у вас действительно есть хорошие варианты. D const и неизменяемые значительно сильнее, чем C/С++, поэтому отбрасывание их не является вариантом. Вы явно исключили изменение использования const в вашем коде.

Вы можете кэшировать результат операции в глобальной хэш-таблице, на основе которой указано значение самой матрицы. Это будет работать для любой комбинации const/неизменяемой. Проблема с этим, конечно, в том, что D не имеет самых быстрых хеш-таблиц в мире, и вычисление хеша может быть медленным. Может быть, предварительно вычислить хэш при создании матрицы.

Другим вариантом будет вычисление детерминанта с нетерпением при изменении значения.

Кроме того, я не могу думать ни о чем другом. На самом деле проблема заключается в том, что вы просите компилятор защитить вас от const, а затем попытаетесь вырваться из него. "Правильное" решение, вероятно, просто не использует const.: P

Ответ 3

Будучи прагматичным языком, D обладает способностью отбрасывать const, если вам действительно нужно. Я думаю, что следующее должно работать:

class M {
  bool set;
  real val;

  real D() const {
    if(!set) {
      M m = cast(M)this;
      m.val = this.some_fn();
      m.set = true;
    }
    return this.val;
  }
}

Ответ 4

Я очень рекомендую ответ BCS, поскольку он прост и безопасен, пока не создана неизменяемая /const Matrix.

Еще одна опция, которая помогает сделать даже неизменяемые/константные объекты, остается в силе, это Mutable Template. Или, по крайней мере, это намерение. Есть замечания по поводу проблем.

С помощью этого шаблона требуется, чтобы модификация выполнялась для ссылочного типа, а не значения в функции const. Это означает, что указатели используются и требуют места для каждой матрицы. Это также затрудняет создание неизменяемой /const Matrix, которая не будет segfault, возможно, есть способ сделать это красиво, но я знаю только один для классов.

struct Matrix
{
    double determinant() const
    {
        if ( *m_dirty )
        {
            *m_determinant = 646.363; /* expensive calculation */;
            *m_dirty = false;
        }
        return *m_determinant;
    }

    void set(int i, int j, double x) { *m_dirty = true; }

    Mutable!(bool*) m_dirty;
    Mutable!(double*) m_determinant;
};

Ответ 5

Чтобы подражать логическому const из С++ в D, вы можете использовать наследование для классов:

class ConstMatrix
{
public:
    double det() { // not marked as const!
        /* ... caching code ... */
    }
    /* ... the rest of the logically const interface ... */
}

class Matrix : ConstMatrix
{
public:
    void set( int row, int col, double val ) {
        /* ... */
    }
    /* ... the non-logically const interface ... */
}

В реализации класса ConstMatrix у вас не будет никаких проверок компилятора, если вы не добавите квалификаторы const в сигнатуры функций. Тем не менее, вы получите корректность const для кода клиента, если вы используете ConstMatrix для логически постоянных матриц.

Для структур вы можете использовать технику alias для достижения того же:

struct ConstMatrix 
{
    /* logically const bla bla blub */
}

struct Matrix
{
public:
    alias m this;

    /* non-const bla bla blub */
private:
    ConstMatrix m;
}

Для типов классов вы можете построить другие class es или struct с логической корректностью const следующим образом:

class ConstBiggerClass
{
private:
    ConstMatrix m;
}

class BiggerClass : ConstBiggerClass
{
private:
    Matrix m;
}

Таким образом, компилятор проверяет правильность const для вас. Однако вам понадобится дополнительный элемент данных. Для членов class типа типа class альтернативой будет предоставление функции-члена, которая возвращает элемент данных с правильной константой:

class ConstBiggerClass
{
public:
    void someLogicallyConstFunction( /*...*/ ) { /* ... */ }
protected:
    abstract ConstMatrix getMatrix();
}

class BiggerClass : ConstBiggerClass
{
public:
    void someMutableFunction( /*...*/ ) { /*...*/ }
protected:
    Matrix getMatrix() { return m; }
private:
    Matrix m;
}