LINUX.ORG.RU

C++ нетривиальные static поля класса

 


0

4

Хочу поинтересоваться у опытных старших товарищей, на сколько страшно когда в классе есть статик поля нетривиальных классов.

стандарт_кодинга_гугла говорит что не стоит использовать в качестве глобальных статиков ничего что нетривиально создаётся и разрушается.

Но что делать, если это было бы очень удобно?

Например возьмём простейший случай где в Си++ используются нетривиальные типы - это строки, причем некоторые строки конструируются через конкатинацию других constStaticStrN(constStaticStr1 + constStaticStr2)

Не использовать же Си-строки в 2018 году в Си++.

Я конечно не принципиально против Си-строк, я даже за них, но очень ограниченно - например передать как параметр в какой-нить устоявшийся интерфейс, где действительно нет нужды сначала собирать std строку. - например в std::ifstream - обычно нет смысла передавать std::string (т.к. внутри себя он всеравно работает с си строками, по крайней мере в g++)

Во всех других случаях я не хочу работать с Си строками. В том числе и с константными строками, которые нужно сравнивать с каким-то значением, обращаться к их размеру, конкатенировать в другие (уже не статик) строки - не хочется портить код в данном случае Си-шными string.h и т.п.

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

Итак как поступить с этим, чтобы было гарантированно безопасно и при этом с точки зрения проектирования - красиво и по возможности строки эти хранились исключительно в классе, и никто кроме класса о них не знал?

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

★★★★★

Последнее исправление: bonta (всего исправлений: 3)

Вмеру уродский способ - держать их в скоупе статических функций. Хотя, конкретно для static - проблемы вроде бы и нет с порядком инициализации, пока ты их между разными единицами трансляции не пытаешься использовать по крайней мере.

pon4ik ★★★★★
()
Ответ на: комментарий от pon4ik

Вмеру уродский способ - держать их в скоупе статических функций.

Не, то же самое, clang -Wexit-time-destructors

Например возьмём простейший случай где в Си++ используются нетривиальные типы - это строки, причем некоторые строки конструируются через конкатинацию других constStaticStrN(constStaticStr1 + constStaticStr2)

Частично, такое покрывается с помощью std::string_view. Конкатенировать, конечно не выйдет просто так. Но как замена константным std::string — самое оно

KennyMinigun ★★★★★
()

тут кто-то подкинул здравую идею, но удалил прежде чем я ответил )

«Да, похоже что он тривиальный (конструктора constexpr), и при этом в нём доступно все то что мне нужно от const std::string.

Но это вроде 17-й стандарт, у меня ограничение - не новее 14го :(

» это я о basic_string_view, если бы я мог на 17-м это бы подошло.

пока ты их между разными единицами трансляции не пытаешься использовать по крайней мере.

Вот тут я уже не компитентен и не очень понимаю механику. Например статик члены инициализируются, во всех единицах трансляции, инициализируются точно до int main(), ну как я понимаю. Т.е. перед тем как main начнет действовать и работать с экземлярами класса, пусть даже из разных потоков, уже типа будет проинциализированны статик поля класса этих экземляров. Но как я понял из ссылки на гугл_си++_гайд, что это не гарантированно. Я хз :)

bonta ★★★★★
() автор топика
Последнее исправление: bonta (всего исправлений: 1)
Ответ на: комментарий от KennyMinigun

Не, то же самое, clang -Wexit-time-destructors

https://stackoverflow.com/questions/14335494/how-to-deal-with-exit-time-destr...

Вот это поворот, из этого следует что «синглтон Мейерса» - это антипаттерн антипаттерна :) (ну сейчас все активнее можно встретить что синглтон это антипаттерн - т.к с ним тяжело тестить например и т.п.)

Т.е. если нужен синглтон то в общем случае лучше предпочесть стандартный вариант, чем более краткословный и казалось бы красивый - Мейерса

bonta ★★★★★
() автор топика
Ответ на: комментарий от bonta

это не гарантированно

Не гарантирован порядок между единицами трансляции. Т.е. при инициализации одной переменной, может быть использована другая, которая ещё не была проинициализирована. Если строковые констанды все в одном месте, то такого не будет (хотя может можно как-то извратиться).

xaizek ★★★★★
()
Последнее исправление: xaizek (всего исправлений: 2)
Ответ на: комментарий от KennyMinigun

Ты видимо не понял моё предложение...

Сделать соглашение в contributing, что доступ к нетривиальным статическим обьектам, только через статические функции или шаблонные обёртки над таковыми. Читай через boost::singleton облегчённый. В простейшем случае типа так:

// Foo.hpp
struct Foo {
static std::string& static_foo();
};

// Foo.cxx
std::string& Foo::static_foo() {
    static std::string foo = "Someval";
    return foo;
}

// Bar.hpp
struct Bar {
    static std::string& static_bar();
};

// Bar.cxx
std::string& Bar::static_bar() {
    static std::string bar = Foo::static_foo() + " how to break?";
    return bar;
}

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

В теории, кажется, что можно будет писать в духе:

static Wrap<std::string> foo ="Foo"; // ну правда слегка масло маслянное
pon4ik ★★★★★
()

Использовать статики небезопасно если они зависят друг от друга, поскольку порядок конструирования и разрушения не определён (между отдельными юнитами, вообще говоря).

Например возьмём простейший случай где в Си++ используются нетривиальные типы - это строки, причем некоторые строки конструируются через конкатинацию других constStaticStrN(constStaticStr1 + constStaticStr2)

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

И вообще, зачем тебе тут константа с конкатенацией, а не функция, её возвращающая?

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

А их вообще не надо конструировать - объявляй их как char*. Конструирование кучи строк даёт ощутимый оверхед при старте приложения и, вообще говоря, не нужна.

не хочется портить код в данном случае Си-шными string.h и т.п.

И не нужно использовать string.h. У std::string почти у всех методов которые принимают строку есть перегрузки принимающие char*.

Итак как поступить с этим, чтобы было гарантированно безопасно и при этом с точки зрения проектирования - красиво и по возможности строки эти хранились исключительно в классе, и никто кроме класса о них не знал?

Я даже больше скажу: их даже в классе не нужно хранить. Их можно вообще не хранить.

// myclass.cpp

#include "myclass.h"

namespace {
  std::string GetFoo() { return "Foo"; }
  std::string GetBar() { return "Bar"; }
  std::string GetBaz() { std::stringstream ss; ss << 1 << "+" << 1 << "=" << (1+1); return ss.str(); }
  std::string GetFooBar() { return GetFoo() + GetBar(); }
}

void MyClass::UseString() {
  std::cerr << GetFooBar() << '\n';
}
slovazap ★★★★★
()
Ответ на: комментарий от slovazap

И вообще, зачем тебе тут константа с конкатенацией, а не функция, её возвращающая?

Потому что хотелось бы избежать каждый раз конструирование строк, а так

std::string GetFoo() { return "Foo"; }

на сколько я знаю, будет вызываться конструктор всякий раз при вызове GetFoo. А если сделать const std::string &GetFoo() - то уже лишняя прослойка возвращающая всё тот же статик, хотя возможно и не лишняя, еще не совсем въехал в то что описал pon4ik

А их вообще не надо конструировать - объявляй их как char*. Конструирование кучи строк даёт ощутимый оверхед при старте приложения и, вообще говоря, не нужна.

Я заметил что в свои методы с сигнатурой void foo(const std::string &) я могу передавать const char* - но ведь тогда работает оператор приведения типа const char * -> std::string - что есть как раз, как мне кажется, лишняя трата производительности, чем при старте проконструировать, т.к. всякий раз когда передается си строка в ссылку на std строку, то вызывается конструктор std стрки. Кстати время старата как раз самое не приоритетное - приоритетно чтобы работало быстро уже после всей загрузки (ну и код при этом не сильно сишный был, конечно :) )

Но если опереться на эти два высказывания:

поскольку порядок конструирования и разрушения не определён (между отдельными юнитами, вообще говоря

и xaizek тоже пишет что

Не гарантирован порядок между единицами трансляции

То в общем теперь понятно почему гугл не рекомендует, и понятно что если в только в одной единице (а они именно в одной и будут) то всё ок. Можно сказать что вопрос решен - оставляю всё как есть, со спокойной совестью :)

bonta ★★★★★
() автор топика
Последнее исправление: bonta (всего исправлений: 2)
Ответ на: комментарий от slovazap

но кстати говоря, да, есть и операторы сравнения между std::string и const char *, и наверное не стоит думать что внутри себя они си строку преобразуют к std а и сравнивают, скорее берут от std с_str()

это да но, там у методов такой дизайн что статик строки я не беру напрямую из класса - а из входящих параметров метода беру их - а там уже конструирование будет если си строку в ссылку на std передать

Ну. т.е. я как бы делал такой дизайн методов чтобы он максимально прозрачный был. Т.е .статики они вроде как и как глобальные переменные в данном случае, но при этом прозрачно попадают в методы и не создают какой-то скрытой связанности с собой. В итоге это еще одна причина по котрой мне не совсем подходит const char *, ну и плюс strlen не хочу, а иногда нужен размер.

А так да, если бы я не передавал в методы их в параметрах, а обращался к области видимости класса - то можно было вполне себе - т.к. операторы конкатинации и сравнения есть между std и cи строками.

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

bonta ★★★★★
() автор топика
Последнее исправление: bonta (всего исправлений: 1)

Если тебе надо глобальные данные заведи отдельный объект который будет тебе их давать. А вообще стоит задуматься о дизайне,зачем тебе такое надо.

invy ★★★★★
()

Считаю статические члены класса злом, т.к. они делают архитектуру не гибкой. Рефакторинг потом дорого обходится.

invy ★★★★★
()
Ответ на: комментарий от invy

Только для того чтобы они создавались один раз и не более того (ну и не было дополнительных сущностей).

А так да, со всем согласен.

А дизайн как раз разработан так что ему не важно статик это члены класса или взято из внешнего по отношению к классу (мой коммент выше)

bonta ★★★★★
() автор топика

Эм, с C++11 появились же Magic Static.

Если есть локальная статическая переменная внутри функции, то она гарантированно будет инициализироваться thread-safe

Т.е. в современных плюсах код вида

const std::string &getStr() {
  static std::string str = "AAAAA";
  return str;
}

потоко безопасен. При этом, не важно, сложный ли объект конструируется или нет.

http://blog.mbedded.ninja/programming/languages/c-plus-plus/magic-statics

Другое дело, что не все компиляторы это поддерживают. Нужно смотреть.

Коллега жаловался, что у него были какие-то проблемы при сборке проектов в MSVS под WinXP

Deleted
()
Ответ на: комментарий от bonta

Да, тоже когда узнал очень долго удивлялся.

По идее, теперь все эти сингелтоны Майерса и прочая double-check locking лабудень теперь не нужна.

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

+ даже уже к инициализированной переменной доступ тоже должен идти с какими-то барьерами, если операции записи с указателями на платформе не атомарны.

Deleted
()
Ответ на: комментарий от Deleted

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

Deleted
()

Проблема со статическими членами не только в порядке создания, но и в порядке удаления. Для «волшебных» C++11 статических переменных в функции порядок удаления будет противоположен порядку создания, что для сложных случаев зависимостей бывает неверно. Проблема гуглится по словам «phoenix singleton».

Кроме того, деструкторы всех объектов вызываются в том потоке, который сделал exit() (или который завершил main()), остальные detached потоки могут в это время пытаться использовать освобождаемые объекты.

Поэтому если хочется, чтобы программа завершалась корректно, лучше писать что-то типа такого:

const string &GetMyString() {
  static auto *ret = new string(a + b);
  return *ret;
}
и полагаться на то, что ОС очистит память.

vzzo ★★★
()
Ответ на: комментарий от x905

Имхо, ты путаешь кроссплатформенность и портабельность. Хотя тут спорный вопрос, можно и компилятор платформой обозвать при большом желании.

а есть другие способы

boost::singleton?

Я там выше писал, если покритикуешь конструктивно - буду благодарен.

pon4ik ★★★★★
()
Ответ на: комментарий от pon4ik

boost::singleton

может быть решением, не вдавался в подробности реализации
но несколько перегружено шаблонами )

x905 ★★★★★
()

Но что делать, если это было бы очень удобно?

Цэ ключевая фраза. Удобно? Делай. Правда, надо оценить корректность суждений о удобстве.

deep-purple ★★★★★
()
Ответ на: комментарий от deep-purple

На самом деле, на XP всем давно уже пофиг. Проблемы с 2003 сервером. Его сейчас все еще много где используют в малом и не очень бизнесе и, пока работает, переходить с него не собираются.

2003 сервер - по сути таже XP. Так что, считай, если у тебя самописное плюсовое серверное приложение, все еще приходится поддерживать.

Deleted
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.