пятница, декабря 26, 2008

Статическая переменная в функции класса

Небольшой С++ паззл. Определим класс

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 коммент.:

plakhov комментирует...

Алёна, в этот раз я тебя не понял :(

Что предполагалось-то получить? По копии статической переменной в каждом экземпляре класса? Этакий hidden member? Вообще было бы прикольно для всяких дебажных фичей, но понятно, почему такого быть не может: нельзя будет посчитать sizeof, не имея доступ ко всем определениям методов.

rageous комментирует...

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

Алёна комментирует...

2plakhov:

Что предполагалось-то получить? По копии статической переменной в каждом экземпляре класса? Этакий hidden member?

Да.

Не могу сказать, что что-то
"предполагалось". Просто не всегда очевидно, что это не так.

Алёна комментирует...

2 rageous:

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

Почему не вынести эту переменную в класс? Читаться такой код точно будет лучше...

Сергей | codeBlogZ комментирует...

Удобно использовать данную технику в собственных "умных" указателей.

Rageous комментирует...

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

Алёна комментирует...

2 Rageous:

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

Угу, ясно.

Programmist комментирует...

Мммм... А что тут не очевидного? Если у нас static переменная в функции - она одна на все вызовы функции. Если среди мемберов класса - она одна на все мемберы. Что не так?

Гораздо интереснее эффект от такой штуки:

int a[10], i=0;
while(i<10) a[i] = i++;

Угадайте, что здесь не так?



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

Programmist комментирует...

Блин, не на все мемберы, а на все экземпляры класса :)

Алёна комментирует...

2 Programmist:

Мммм... А что тут не очевидного? Если у нас static переменная в функции - она одна на все вызовы функции.

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

Гораздо интереснее эффект от такой штуки:

int a[10], i=0;
while(i<10) a[i] = i++;

Угадайте, что здесь не так?

Точки следования здесь не так. Undefined behavior.

Surzh комментирует...

А как же паттерн Singletone?

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

А так все просто и понятно.

Raider комментирует...

По теме поста: static-переменная внутри функции-члена имеет право на жизнь - это просто синглтон с ограниченной видимостью. Если нет предубеждений на счет синглтонов, то вполне можно использовать.

void CBase::BaseTest()
{
static boost::optional<Config> cfg;

if (!cfg) cfg = LoadConfig();

// ... use cfg ...
// ;)
}

Алёна комментирует...

2 Surzh:

А как же паттерн Singletone?

Классический синглтон всё-таки не так выглядит...

Но можно его определить как Raider дальше описал, да.

Abu комментирует...

Благодаря Алене, читаю код из примеров и вспоминаю светлые времена и думаю - елы-палы - а ведь что-то и мне тут понятно!

poige комментирует...

Старая, добрая фича. Удивлён, что для тебя, с таким-то стажем, она ещё и новая.

Вадим комментирует...

Гы..по-моему эту рюшку часто используют. Например чтобы знать сколько раз вызван этот твой метод.

SkyWinder комментирует...

спасибо, узнал где спрятаны еще одни грабли :)

uber1337 комментирует...

Вот простой пример:

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 строковыми идентификаторами без потери производительности.

Vadim комментирует...

В Смоллтоке это была бы просто переменная класса.

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

Монстрообразный, страшный язык :)

Алёна комментирует...

2 uber1337:

Вот простой пример:

void doStuff()


В моем примере речь идёт о функции класса, это важно.

ilya-314 комментирует...

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

foreground комментирует...

Я вот читаю и медленно но верно фигею .. . - а зачем такое надо вобще? Что вам функции посчитать захотелось? Вы пишите отладчик? По-моему достаточно нефичёвого ООП для спокойной жизни .

rageous
"удобных и полезных фич с++, которая никак не относится к проектированию по-моему."

А к чему такое относится?

and85 комментирует...

Ох и не завидую я тому, кому подобное дебажить приходится...

В строгом Delphi я не могу такое начудить, по причине того, что переменные класса можно объявить только в описании класса, и вопросов не возникает:)

Были подобные проблемы у людей, которые во времена, когда в Delphi попросту небыло методов и полей уровня класса, замещали их не глобальными переменными, а типизированными константами (их в делфи при определенных опциях компилятора изменять можно)

deeonis комментирует...

А почему переменная не инициализируется нулем при втором вызове функции?

metronix комментирует...

"Редко когда такой код пишется намеренно, люди так не проектируют." Изредка использую.. честно говоря не понял что же здесь такого.. я думаю тот кто не знаю назначения слова static использовать его не будет, а те кто знают и так понимают что к чему.. или я не понял суть проблемы??

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

Алёна комментирует...

2 deeonis:

А почему переменная не инициализируется нулем при втором вызове функции?

Потому что она статическая. Инициализируется по Стандарту, согласно 6.7.4.

2 metronix:

я думаю тот кто не знаю назначения слова static использовать его не будет, а те кто знают и так понимают что к чему..

Люди, которые учатся, постоянно используют то, что не очень понимают. Это не только применительно к static'ам.

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

пожалуйста :-)

pesec комментирует...

Ещё интереснее будет, если сделать кое-какие замены.
class CBase ==> template <typename T> class CBase
...
CBase b1 ==> CBase<int> b1
CBase b2 ==> CBase<double> b2

zorgg комментирует...

> Редко когда такой код пишется намеренно, люди так не проектируют.

Я, вероятно, баклажан. Или утконос.

Алёна комментирует...

2 zorgg:

> Редко когда такой код пишется намеренно, люди так не проектируют.

Я, вероятно, баклажан. Или утконос.


Дык эта, расскажи где ты так делал, почему было выбрано именно такое решение.

zorgg комментирует...

2Алёна: Иногда удобно делить переменную между всеми экземплярами класса. Иногда удобно, когда это локальная переменная метода. Ну, счетчик какой-нибудь, например. Или, там, какой-нибудь указатель на вершину общего хранилища. Да мало ли - синглтон хотя бы или ленивая копия.

Я уже не помню, что имел в виду когда писал предыдущий комментарий - туча времени прошло - но я явно пишу время от времени именно такой код :)

Рома комментирует...

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

Если такой код решает проблему лучше, чем остальные способы почему бы и не применить.

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

Но я думаю тут дело в понимании(или непонимании), как эффективно использовать язык в конкретной ситуации.

Анонимный комментирует...

Programmist:
int a[10], i=0;
while(i<10) a[i] = i++;

всегда думал что существует 2 оператора ++х и х++, прединкримент и постинкримент. Соответственно первый увеличит переменную до её использования, а второй после...

По теме:
Если спроектированный класс подразумевает использование общих для всех объектов класса переменных, то почему не объявлять их в хэдэре ? - засорение. Тогда что вы спроектировали ?