Должен ли я использовать статические переменные в своих функциях для предотвращения повторного вычисления значений? - PullRequest
9 голосов
/ 13 июня 2019

Я уже некоторое время пишу код на C ++, но есть кое-что, о чём я некоторое время удивлялся, не находя четкого ответа.

Моя точка зрения такова: скажем такУ меня есть функция (это может быть метод, может быть static, но не обязательно), и эта функция использует некоторые «тяжелые» объекты (например, строку, которую невозможно определить легко во время компиляции, но это постоянно на протяжении всего выполнения).Вот пример, с которым я действительно столкнулся:

/* Returns an endpoint for an API
 * Based on the main API URL (getApiUrl())
 */
virtual QString getEndPointUrl() const override
{
    QString baseUrl = getApiUrl();
    QString endpointUrl = QString("%1/%2").arg(baseUrl, "endpoint");
    return endpointUrl;
}

Это, конечно, просто пример (я знаю, что у QString есть свои необычные функции управления памятью Qt, но давайте признаем, что мыработа с основными объектами).

Хорошая ли идея сделать следующее?

virtual QString getEndPointUrl() const override
{
    /* We determine baseUrl only once */
    static const QString baseUrl = getApiUrl();
    /* We compute endpointUrl only once */
    static const QString endpointUrl = QString("%1/%2").arg(baseUrl, "endpoint");
    return endpointUrl;
}

Как вы уже догадались, идея в том, чтобы не определять URL при каждом выполнении getEndPointUrl.

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

Другое дело, что она считается«лучшая» практика иметь функции без сохранения состояния, но я не думаю, что такое поведение можно квалифицировать как «состояние».

РЕДАКТИРОВАТЬ: я просто хотел отметить, что значения, которые я вычисляю,бессмысленно вне функции, иначе они могут быть полем окружающего класса или чего-то еще, но они никогда не используются где-либо еще.

Что вы думаете?

Ответы [ 4 ]

7 голосов
/ 13 июня 2019

Да, абсолютно!

Как всегда, есть компромисс, который вы уже определили.

Но это совершенно нормальный и разумный поступок.Если вам не нужно каждый раз вычислять эти значения, то не надо.

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

Фактически, это приведет к отображению всей функции getEndPointUrl()устарели.Просто будь публичным постоянным членом!Ваше обоснование, что константа «нигде больше не используется», является чем-то вроде кругового аргумента;вы используете данные везде, где используется getEndPointUrl().

4 голосов
/ 13 июня 2019

Это кажется приемлемым вариантом. У меня есть одно предложенное изменение для вашего образца, и оно должно вызывать getApiUrl() в инициализаторе второй статики, делая его единственным инициализатором ... таким образом:

static const QString endpointUrl = QString("%1/%2").arg(getApiUrl(), "endpoint");

Это на один объект меньше, чем на всю жизнь вашей программы.

При кэшировании со статикой возникает ряд проблем:

  1. Вы теряете контроль над временем жизни этого объекта. Это может или не может быть проблемой в зависимости от того, нуждается ли этот объект в указателе / ​​ссылке на что-то другое, чтобы правильно очистить после себя.
  2. Я не верю, что есть какие-либо гарантии в отношении порядка деконструкции статических объектов ... Может быть необходимо дополнительно обернуть вещи в shared_ptr, чтобы гарантировать, что ресурсы не будут извлечены из-под ваших статических объектов.
  3. Кэши, как правило, часто обеспечивают способ очистки их сохраненных значений и их повторного заполнения. Для статики такого механизма не существует, в частности const static s.

РЕДАКТИРОВАТЬ: Ходят слухи, что безопасность потока не является проблемой.

Но если ни одна из этих проблем не применима в вашем конкретном случае использования, тогда непременно используйте static.

РЕДАКТИРОВАТЬ: Нетривиальный комментарий комментарий:

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

Визуализация, изменяющая вашу программу так, что ваша система загрузки ресурсов теперь запускается перед вашей системой регистрации. Вы устанавливаете точку останова и шагаете по новому коду, после чего вы видите, что все в порядке. Вы заканчиваете сеанс отладки, запускаете свои модульные тесты, и они все проходят. Проверьте в источнике, и ночные интеграционные тесты не пройдут ... , если вам повезет . Если это не так, ваша программа начинает аварийно завершать работу при выходе перед клиентами.

Оказывается, ваша система ресурсов пытается что-то записать при выключении. Boom. Но эй ... все по-прежнему работает нормально! Зачем исправлять это, верно?

О, и получается, что то, что ваша система ресурсов пыталась зарегистрировать, было ошибкой, которая будет проблемой только для вашего крупнейшего клиента.

Уч.

Не будь тем парнем. Не зависит от статического порядка деструктора.

(Хорошо, теперь представьте, что порядок создания / разрушения объектов не является детерминированным, потому что некоторые из этих функций со статическими объектами вызываются из разных потоков. С кошмарами еще нет?)

2 голосов
/ 13 июня 2019

Для вашего конкретного примера

В вашем примере кэширование практически не повлияет на производительность, поэтому использование static, вероятно, является единственным методом, который стоит потраченных усилий.

Если ваши вычисления действительно былидорого, вот некоторые мысли об этом способе кэширования в целом:

В общем

Плюсы:

  • Это автоматически поточно-ориентированный
  • Это очень легко использовать

Минусы:

  • Техническое обслуживание
    1. Эффективно синглтон со всеми его проблемами
      • Может стать неожиданной ошибкой, если вдруг getUrl() потребуется вернуть разные значения
      • Не подходит для юнит-тестов
      • и т. Д.
    2. Многолюди не знают, что делает ключевое слово static
    3. (Если вы несколько раз будете ссылаться на статическую библиотеку, вы получите несколько экземпляров переменной. Вероятно, нарушение ODR )
  • Функция
    • Может быть, это нежелательноed для первого вызова функции, чтобы занять больше времени
  • Structural
    • Вероятно, использовать после освобождения, если кэшированный объект ссылается на что-то в стеке.

^ (Обратите внимание, что многие из них не применяются в определенных обстоятельствах)

Альтернативы

В порядке предпочтения:

  1. Кэшировать значение всякий раз, когда значение getUrl () установлено / изменено.
  2. Кэшировать значение в объекте
  3. Использовать кеш (какой-то map<string, shared_ptr<void>> или что-то), который вы Внедрить (Избыток за это)
1 голос
/ 13 июня 2019

Это зависит от вашего контекста.Как вы правильно заметили, вы торгуете использованием памяти для скорости.Итак, что важнее (если вообще, даже) в вашем контексте?Вы находитесь на горячем пути программы с низкой задержкой?У вас мало памяти?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...