12.14 – Статические функции-члены

Добавлено 10 июля 2021 в 12:19

Статические функции-члены

В предыдущем уроке (12.13 – Статические переменные-члены) вы узнали, что статические переменные-члены являются переменными-членами, которые принадлежат классу, а не объектам класса. Если статические переменные-члены являются открытыми, мы можем получить к ним доступ напрямую, используя имя класса и оператор разрешения области видимости. Но что, если статические переменные-члены являются закрытыми? Рассмотрим следующий пример:

class Something
{
private:
    static int s_value;
 
};
 
// инициализатор, это нормально, даже если s_value
// является закрытым членом, поскольку это определение
int Something::s_value{ 1 }; 
 
int main()
{
    // как мы можем получить доступ к переменной
    // Something::s_value, если она закрытая?
}

В этом случае мы не можем получить доступ к Something::s_value напрямую из main(), потому что это закрытая переменная-член. Обычно мы получаем доступ к закрытым членам через открытые функции-члены. Хотя мы могли бы создать обычную открытую функцию-член для доступа к s_value, но тогда нам нужно было бы создать экземпляр объекта класса, чтобы использовать эту функцию! Мы можем добиться большего. Оказывается, мы также можем сделать статическими и функции.

Как и статические переменные-члены, статические функции-члены не привязаны к какому-либо конкретному объекту. Вот приведенный выше пример со статической функцией-членом доступа:

class Something
{
private:
    static int s_value;
public:
    static int getValue() { return s_value; } // статическая функция-член
};
 
int Something::s_value{ 1 }; // инициализатор
 
int main()
{
    std::cout << Something::getValue() << '\n';
}

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

У статических функций-членов нет указателя *this

У статических функций-членов есть две интересные особенности, на которые стоит обратить внимание. Во-первых, поскольку статические функции-члены не прикреплены к объекту, у них нет указателя this! Это должно быть понятно, если подумать – указатель this всегда указывает на объект, над которым работает функция-член. Статические функции-члены не работают с объектом, поэтому указатель this не требуется.

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

Еще один пример

Статические функции-члены также могут быть определены вне объявления класса. Это работает так же, как и для обычных функций-членов.

Например:

class IDGenerator
{
private:
    static int s_nextID; // объявление статического члена
 
public:
     static int getNextID(); // объявление статической функции
};
 
// Определение статического члена вне класса.
// Обратите внимание, что здесь мы не используем ключевое слово static.
// Мы начнем генерировать идентификаторы с 1
int IDGenerator::s_nextID{ 1 };
 
// Определение статической функции вне класса.
// Обратите внимание, что мы не используем здесь ключевое слово static.
int IDGenerator::getNextID() { return s_nextID++; } 
 
int main()
{
    for (int count{ 0 }; count < 5; ++count)
        std::cout << "The next ID is: " << IDGenerator::getNextID() << '\n';
 
    return 0;
}

Эта программа печатает:

The next ID is: 1
The next ID is: 2
The next ID is: 3
The next ID is: 4
The next ID is: 5

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

Предупреждение о классах со всеми статическими членами

Будьте осторожны при написании классов со всеми статическими членами. Хотя такие «чисто статические классы» (также называемые «моносостояниями») могут быть полезны, они также имеют некоторые потенциальные недостатки.

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

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

C++ не поддерживает статические конструкторы

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

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

class MyClass
{
public:
    static std::vector<char> s_mychars;
};
 
// инициализируем статическую переменную в точке определения
std::vector<char> MyClass::s_mychars{ 'a', 'e', 'i', 'o', 'u' };

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

class MyClass
{
public:
    static std::vector<char> s_mychars;
};
 
std::vector<char> MyClass::s_mychars{
  []{ // Список параметров для лямбда-выражений без параметров можно опустить.
      // Внутри лямбды мы можем объявить другой вектор и использовать цикл.
      std::vector<char> v{};
      
      for (char ch{ 'a' }; ch <= 'z'; ++ch)
      {
          v.push_back(ch);
      }
      
      return v;
  }() // Сразу вызываем лямбду
};

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

class MyClass
{
public:
    static std::vector<char> s_mychars;
 
public:
    // определяем вложенный класс с именем init_static
    class init_static
    {
    public:
        // инициализирующий конструктор инициализирует
        // нашу статическую переменную
        init_static() 
        {
            for (char ch{ 'a' }; ch <= 'z'; ++ch)
            {
                s_mychars.push_back(ch);
            }
        }
    } ;
 
private:
    // будем использовать этот статический объект для
    // обеспечения вызова конструктора init_static
    static init_static s_initializer; 
};
 
// определяем нашу статическую переменную-член
std::vector<char> MyClass::s_mychars{};

// определяем наш статический инициализатор, который
// вызовет конструктор init_static, который инициализирует s_mychars
MyClass::init_static MyClass::s_initializer{}; 

Когда определяется статический член s_initializer, будет вызываться конструктор по умолчанию init_static() (поскольку s_initializer имеет тип init_static). Мы можем использовать этот конструктор для инициализации любых статических переменных-членов. Хороший момент в этом решении заключается в том, что весь код инициализации скрыт внутри исходного класса со статическим членом.

Резюме

Статические функции-члены в классе можно использовать для работы со статическими переменными-членами. Их не требуется вызывать именно через объект класса.

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

Теги

C++ / CppLearnCppstaticДля начинающихКласс (программирование)МоносостояниеОбучениеПрограммирование

На сайте работает сервис комментирования DISQUS, который позволяет вам оставлять комментарии на множестве сайтов, имея лишь один аккаунт на Disqus.com.

В случае комментирования в качестве гостя (без регистрации на disqus.com) для публикации комментария требуется время на премодерацию.