Логическая константа в D - PullRequest
13 голосов
/ 19 ноября 2010

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

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

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.

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

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

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

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

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

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

Ответы [ 5 ]

11 голосов
/ 01 декабря 2010

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

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

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

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

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

Учитывая, что отбрасывание const будет работать до тех пор, пока переменная на самом деле не ссылается на неизменяемые данные, можно создать шаблон Mutable, чтобы по существу получить то, что mutable дает вам в C ++ (так чтоэто сделает отбрасывание константы за вас). he_the_great приводит пример такого шаблона в своем ответе.Но использование такого шаблона до сих пор не определено.Использование его на объекте, который на самом деле неизменен, вызовет проблемы. Программист должен убедиться, что он используется правильно.

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

РЕДАКТИРОВАТЬ: Я забыл упомянуть одно предлагаемое решение, которое не нарушает систему типов. Пока вы готовы отказаться от чистоты, вы можете использовать глобальную переменную некоторого разнообразия (будь то в области видимости модуля, переменной класса или переменной структуры) для хранения ваших кэшированных значений. Функция const может свободно использовать и изменять глобальные переменные, поэтому ее можно использовать вместо отсутствующего mutable. Это, однако, означает, что функция не может быть чистой, что также может быть большой проблемой. Это, однако, способ иметь функцию const, которая по-прежнему может изменять необходимые данные, не нарушая систему типов.

4 голосов
/ 19 ноября 2010

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

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

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

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

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

1 голос
/ 19 ноября 2010

Будучи прагматичным языком, 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;
  }
}
0 голосов
/ 15 мая 2013

Для имитации логического const из C ++ в 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 в сигнатуры функций. Однако вы получите постоянную корректность для клиентского кода, если вы используете 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 s с логической константностью следующим образом:

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;
}
0 голосов
/ 01 декабря 2010

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

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

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

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;
};
...