Небольшой С++ паззл. Определим класс
class CBase
{
...
public:
...
void BaseTest()
{
static int i=0;
i++;
cout<<i<<endl;
}
};
А потом где-нибудь в main() напишем следующий код.
CBase b1;
CBase b2;
b1.BaseTest();
b2.BaseTest();
Ну и стандратный вопрос - что будет выведено на экран?
Проблема тут в том, что статическая переменная будет одна на все создаваемые экзепляры класса. Редко когда такой код пишется намеренно, люди так не проектируют. Это либо результат рефакторинга, или остатки кода "сейчас быстренько проверим, а потом поправим".
Если программист ожидал, что статических переменных будет несколько, то ошибка сразу может быть не видна. Пока у вас только один экземпляр класса - всё работает как ожидалось. А если их несколько - программа будет вести себя странно и это будет сложно отловить.
На всякий случай правильный ответ:
1
2
Ссылки по теме:
comp.lang.c++.moderated - static variable in non-static member function
32 коммент.:
Алёна, в этот раз я тебя не понял :(
Что предполагалось-то получить? По копии статической переменной в каждом экземпляре класса? Этакий hidden member? Вообще было бы прикольно для всяких дебажных фичей, но понятно, почему такого быть не может: нельзя будет посчитать sizeof, не имея доступ ко всем определениям методов.
часто так пишу для генерации уникальных id инстансов какого-либо класса, например. да и вообще, это одна из вполне удобных и полезных фич с++, которая никак не относится к проектированию по-моему.
2plakhov:
Что предполагалось-то получить? По копии статической переменной в каждом экземпляре класса? Этакий hidden member?
Да.
Не могу сказать, что что-то
"предполагалось". Просто не всегда очевидно, что это не так.
2 rageous:
часто так пишу для генерации уникальных id инстансов какого-либо класса, например. да и вообще, это одна из вполне удобных и полезных фич с++, которая никак не относится к проектированию по-моему.
Почему не вынести эту переменную в класс? Читаться такой код точно будет лучше...
Удобно использовать данную технику в собственных "умных" указателей.
Ален, потому что для того, чтобы вынести ее в класс, придется лишний раз замусорить хеадер, потом проинициализировать ее в cpp-файле (которого, кстати, может и вовсе не быть) - и лишь потом можно будет использовать. Не вижу в этом всем смысла - разве только чтобы эффект неожиданности свести на нет, но мне кажется, что он и так у большинства не возникнет.
2 Rageous:
Ален, потому что для того, чтобы вынести ее в класс, придется лишний раз замусорить хеадер, потом проинициализировать ее в cpp-файле (которого, кстати, может и вовсе не быть) - и лишь потом можно будет использовать.
Угу, ясно.
Мммм... А что тут не очевидного? Если у нас static переменная в функции - она одна на все вызовы функции. Если среди мемберов класса - она одна на все мемберы. Что не так?
Гораздо интереснее эффект от такой штуки:
int a[10], i=0;
while(i<10) a[i] = i++;
Угадайте, что здесь не так?
Ответ запушу задом наперед:
.ависсам уцинарг аз дохыв тедуб от ,яаварп алачанс илсЕ .итсоньлетаводелсоп йонзар в яинеовсирп итсач тюялсичыв ыротялипмок еынзаР
Блин, не на все мемберы, а на все экземпляры класса :)
2 Programmist:
Мммм... А что тут не очевидного? Если у нас static переменная в функции - она одна на все вызовы функции.
Неочевидно то, что переменная всё та же и для вызовов данной функции из других экземлпяров класса.
Гораздо интереснее эффект от такой штуки:
int a[10], i=0;
while(i<10) a[i] = i++;
Угадайте, что здесь не так?
Точки следования здесь не так. Undefined behavior.
А как же паттерн Singletone?
Помоему, static - очень полезная вещь. Единственная проблема - это работа в нескольких потоках, так как тогда приходится к статическим мемберам лепить соотв. статические критические секции.
А так все просто и понятно.
По теме поста: static-переменная внутри функции-члена имеет право на жизнь - это просто синглтон с ограниченной видимостью. Если нет предубеждений на счет синглтонов, то вполне можно использовать.
void CBase::BaseTest()
{
static boost::optional<Config> cfg;
if (!cfg) cfg = LoadConfig();
// ... use cfg ...
// ;)
}
2 Surzh:
А как же паттерн Singletone?
Классический синглтон всё-таки не так выглядит...
Но можно его определить как Raider дальше описал, да.
Благодаря Алене, читаю код из примеров и вспоминаю светлые времена и думаю - елы-палы - а ведь что-то и мне тут понятно!
Старая, добрая фича. Удивлён, что для тебя, с таким-то стажем, она ещё и новая.
Гы..по-моему эту рюшку часто используют. Например чтобы знать сколько раз вызван этот твой метод.
спасибо, узнал где спрятаны еще одни грабли :)
Вот простой пример:
void doStuff()
{
static int id = SuperBigStore.findSmth("BigStringID"); // seeking for something in a big database
SUPER_VARIABLE var = SuperBigStore.getVarId(id);
// work with var here
...
}
Скажем, при оборачивании в макрос очень удобно работать с user-friendly строковыми идентификаторами без потери производительности.
В Смоллтоке это была бы просто переменная класса.
Меня поражает, как C++ любит решать проблемы, которые сам себе создает. Одна система темплейтов чего стоит.
Монстрообразный, страшный язык :)
2 uber1337:
Вот простой пример:
void doStuff()
В моем примере речь идёт о функции класса, это важно.
Так можно например реализовать счетчик вызовов функции. Пример - нужно сбросить тесты на функцию, имя теста содержит номер вызова.
Я вот читаю и медленно но верно фигею .. . - а зачем такое надо вобще? Что вам функции посчитать захотелось? Вы пишите отладчик? По-моему достаточно нефичёвого ООП для спокойной жизни .
rageous
"удобных и полезных фич с++, которая никак не относится к проектированию по-моему."
А к чему такое относится?
Ох и не завидую я тому, кому подобное дебажить приходится...
В строгом Delphi я не могу такое начудить, по причине того, что переменные класса можно объявить только в описании класса, и вопросов не возникает:)
Были подобные проблемы у людей, которые во времена, когда в Delphi попросту небыло методов и полей уровня класса, замещали их не глобальными переменными, а типизированными константами (их в делфи при определенных опциях компилятора изменять можно)
А почему переменная не инициализируется нулем при втором вызове функции?
"Редко когда такой код пишется намеренно, люди так не проектируют." Изредка использую.. честно говоря не понял что же здесь такого.. я думаю тот кто не знаю назначения слова static использовать его не будет, а те кто знают и так понимают что к чему.. или я не понял суть проблемы??
з.ы. уже давненько почитываю ваш блог, ток вот как-то писать не писал.. спасибо вам за интересные посты!
2 deeonis:
А почему переменная не инициализируется нулем при втором вызове функции?
Потому что она статическая. Инициализируется по Стандарту, согласно 6.7.4.
2 metronix:
я думаю тот кто не знаю назначения слова static использовать его не будет, а те кто знают и так понимают что к чему..
Люди, которые учатся, постоянно используют то, что не очень понимают. Это не только применительно к static'ам.
з.ы. уже давненько почитываю ваш блог, ток вот как-то писать не писал.. спасибо вам за интересные посты!
пожалуйста :-)
Ещё интереснее будет, если сделать кое-какие замены.
class CBase ==> template <typename T> class CBase
...
CBase b1 ==> CBase<int> b1
CBase b2 ==> CBase<double> b2
> Редко когда такой код пишется намеренно, люди так не проектируют.
Я, вероятно, баклажан. Или утконос.
2 zorgg:
> Редко когда такой код пишется намеренно, люди так не проектируют.
Я, вероятно, баклажан. Или утконос.
Дык эта, расскажи где ты так делал, почему было выбрано именно такое решение.
2Алёна: Иногда удобно делить переменную между всеми экземплярами класса. Иногда удобно, когда это локальная переменная метода. Ну, счетчик какой-нибудь, например. Или, там, какой-нибудь указатель на вершину общего хранилища. Да мало ли - синглтон хотя бы или ленивая копия.
Я уже не помню, что имел в виду когда писал предыдущий комментарий - туча времени прошло - но я явно пишу время от времени именно такой код :)
Нельзя однозначно сказать, что так делать нельзя, или всегда так делать.
Если такой код решает проблему лучше, чем остальные способы почему бы и не применить.
Некоторые не любят глобальные переменные, статические переменные, множественное наследованию, те же синглтоны.
Но я думаю тут дело в понимании(или непонимании), как эффективно использовать язык в конкретной ситуации.
Programmist:
int a[10], i=0;
while(i<10) a[i] = i++;
всегда думал что существует 2 оператора ++х и х++, прединкримент и постинкримент. Соответственно первый увеличит переменную до её использования, а второй после...
По теме:
Если спроектированный класс подразумевает использование общих для всех объектов класса переменных, то почему не объявлять их в хэдэре ? - засорение. Тогда что вы спроектировали ?
Отправить комментарий