9

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

hero
  • 113
  • сделайте статическую переменную и меняйте при первом вызове ей значение – splash58 Apr 23 '16 at 16:39
  • @splash58 хранить ради этого целую переменную???!!! – hero Apr 23 '16 at 16:44
  • @hero это простейший вариант. – AivanF. Apr 23 '16 at 16:45
  • @hero я думаю, любой другой код займет больше, чем байт на класс – splash58 Apr 23 '16 at 16:45
  • Формально вызвать-то можно будет, другое дело, что она ничего не будет делать. Но вопрос-то сформулирован не так... – Harry Apr 23 '16 at 17:00
  • 4
    Целую переменную? Вы наверное имели ввиду всего одну переменную? Вам жалко одного байта? – VladD Apr 23 '16 at 17:06

2 Answers2

13

Для этого в C++ есть специальная функция и флаг, делается это так:

std::once_flag flag;
//...
class Class
{
    Class()
    {
        std::call_once(flag, [this]{ SomeMethod(); });
    }

    void SomeMethod()
    {...}
}

Таким образом мы вызовем SomeMethod() в конструкторе однажды, но это не запретит вызывать этот метод в других местах программы, поэтому его можно сделать приватным, но это не запретит вызов приватного метода в других методах класса. Для того, чтобы полностью исключить повторный вызов какого-либо кода, нужно весь этот код поместить в лямбду, которая передаётся в std::call_once

std::once_flag flag;
//...
class Class
{
    Class()
    {
        std::call_once(flag, [this]
        { 
            // Тут будет код, который нужно вызывать лишь единожды
        });
    }
}
ixSci
  • 23,825
  • Можете показать, как это в моём случае будет выглядеть? – hero Apr 23 '16 at 17:17
  • @hero, для начала покажите Ваш случай. У Вас в вопросе слишком мало данных – ixSci Apr 23 '16 at 17:42
  • Просто непонятно для чего [], функция должна вызываться в функторе что ли? – hero Apr 23 '16 at 17:49
  • @hero, это лямбда. Необязательно её использовать, конечно, просто так проще. Всё зависит от того, какую функцию Вы вызываете – ixSci Apr 23 '16 at 17:50
  • Я вызываю функцию из области видимости класса при его создании один раз – hero Apr 23 '16 at 17:53
  • @hero, метод класса, или свободная функция? – ixSci Apr 23 '16 at 17:54
  • Метод класса... – hero Apr 23 '16 at 17:56
  • @hero, обновил ответ – ixSci Apr 23 '16 at 17:58
  • Спасибо!!!!!!!! – hero Apr 23 '16 at 17:59
  • 2
    Формально никто не мешает вызвать SomeMethod отдельно - http://ideone.com/hPeUbN Вопрос же стоит "которую в дальнейшем нельзя будет вызывать (ни с помощью данного экземпляра, ни других экземпляров этого класса)". В лучшем случае это должна быть лямбда, область видимости которой - конструктор, но никак не функция из области видимости класса. По-моему, вопрос стоит переформулировать более корректно :) – Harry Apr 23 '16 at 18:09
  • @Harry, ну тогда просто перенести вызов call_once в функцию, думаю, что автор разберётся. – ixSci Apr 23 '16 at 18:13
  • @hero, прочитайте 2 комментария выше – ixSci Apr 23 '16 at 18:13
  • @ixSci Понимаете, лично я воспринял вопрос так, что запрет повторного вызова - на уровне компилятора, а это невозможно ну никак. Imho. Наверное, я чересчур формалист, да, но ведь и "чтоб больше нельзя было вызвать" - это полный запрет на вызов. А не "чтобы вызов вернулся, ничего не делая", например... – Harry Apr 23 '16 at 18:18
  • @Harry, а зачем такое может быть? Чем ситуации будут отличаться? Человек формулирует вопрос в рамках своего понимания темы, озвучивает желание — оно не всегда чёткое, поэтому ожидать чётких и формальных вопросов не стоит, я считаю. – ixSci Apr 23 '16 at 18:24
  • @Harry someMethod() у меня в private секции – hero Apr 23 '16 at 18:37
  • 1
    @ixSci Ну, я сам иногда задаю очень теоретичные вопросы... :) И меня этот вопрос заинтересовал именно с этой точки зрения. По здравом размышлении пришел к выводу, что для функции-члена это нереально. Но хотел бы убедиться, чтоб гуру то же самое сказали :) – Harry Apr 23 '16 at 19:17
  • @ixSci, Это всё хорошо, но! разве если экземпляр класса был создан до того, как установлен флаг, вызов не произойдёт? – Isaev Apr 28 '16 at 13:54
  • @Isaev, не совсем понял, что Вы имеет в виду, но тот код, что находится в лямбде, которая передаётся в call_once, гарантировано будет вызван лишь один раз. – ixSci Apr 28 '16 at 15:22
7

Просто заведите статическую переменную-флаг и обрабатывайте его:

foo::foo()
{
    static bool once = true;
    if(once)
    {
        once = false;
        bar();
    }
}

Второй вариант более элегантный, но работает только если bar возвращает какое-либо значение, то есть не void:

int bar();

foo::foo()
{
    static const auto once = bar();
}
Cerbo
  • 6,863
  • Интересно, начнутся ли тут сейчас дебаты о singleton-ах? – avp Apr 23 '16 at 19:29
  • @avp, а больше не о чем дебатировать, синглтоны теперь в C++ имеют один вид и пишутся элементарно. – ixSci Apr 24 '16 at 04:27
  • @ixSci, и как же их теперь канонически надо писать? – avp Apr 24 '16 at 09:20
  • @avp, static Object& instance(){static Object instance; return instance;}. По вкусу, можно возвращать указатель – ixSci Apr 24 '16 at 09:22
  • @ixSci, тогда я не что-то не понимаю. Вопрос ТС это фактически про singleton. Почему же среди ответов нет Вашего варианта, а есть 2 других и оба сильно заплюсованы? – avp Apr 24 '16 at 09:26
  • @avp, вопрос можно свести к синглтону, но он не о нём. Так, Cerbo, в целом и привёл пример, сведённый к синглтону, только вместо объекта, он использовал функцию. С чистым синглтоном получилось бы больше кода(добавлять новый класс совершенно не имеет смысла, в рамках данной задачи) – ixSci Apr 24 '16 at 09:33
  • @ixSci, дело в том, что в многопоточной среде этот код в принципе не безопасен. Поэтому я и упомянул синглтон, но похоже, что тут никто так глубоко не смотрит. – avp Apr 24 '16 at 09:57
  • @avp, мой код полностью потокобезопасен. Про код Cerbo не уверен, но скорее всего тоже должен быть безопасен — нужно штудировать стандарт. – ixSci Apr 24 '16 at 10:03
  • @ixSci, я про Cerbo. Для безопасности там нужен мьютекс (где и как это написать в крестах -- другой вопрос). Сейчас, если переключение потоков (по прерыванию от таймера) произойдет после if, но до записи false (причем такой, что другие потоки это увидят) bar() может выполниться не один раз. – avp Apr 24 '16 at 10:18
  • @avp, я говорил про второй кусок. Он, насколько я понимаю, потокобезопасен. Чтобы удостоверится, нужно найти в стандарте, что вызов bar является частью инициализации(в чём я почти на 100 уверен). Первый кусок, да, не является безопасным, с точки зрения многопоточной среды. – ixSci Apr 24 '16 at 10:21
  • @ixSci, второй да. Понятно, что инициализация статиков происходит однократно да вызова main. И вот тут большой вопрос, какое состояние памяти видит bar. (логически продолжая подобные рассуждения приходим к выводу, что С++ не годится для программирования сложных вещей). – avp Apr 24 '16 at 10:28
  • @avp, без обид, но это не C++ не предназначен, а просто Вы его знаете не очень хорошо. Не будет тут статик проинициализирован до main, он будет проинициализирован в момент вызова foo и только один раз, гарантировано только одним потоком. Это гарантирует стандарт C++. – ixSci Apr 24 '16 at 11:36
  • А почему можно свести вопрос к сингтону? Вообще поведение, возможно, необходимое для регистрации каких-то уникальных ID в какой-то фабрике при первом использовании класса. Экземпляров может быть много, а вот выполнять регистрацию можно только раз. @avp, да, тов. ixSCi прав по поводу статиков. Всегда было, что статик уровня функции инициализируюется при первом вызове этой функции, а уровня единицы трансляции - до main. C++11 стал гарантировать что такие инициализации будут потокобезопасными. (почему-то опубликовалось в виде ответа) – Monah Tuk Apr 25 '16 at 08:52
  • @ixSci, ага (раз гарантируется такое поведение), тогда будет работать. Но С++ все равно не годиться, он слишком сложен, слишком много деталей. Где найти за приемлемую плату грамотных людей на поддержку? Недаром, даже в opensource популярность С и С++ одинакова, а в Tiobe C популярней вдвое. – avp Apr 25 '16 at 10:03