Напоминание про inline и static. Синглтон.

Материал из SEWiki
Перейти к: навигация, поиск

Inline

Рассмотрим некоторую inline функцию:

inline void f() {}

Где ее разместить: в cpp или в header'e?

1) При определении обычной функции в заголовочном файле может произойте так, что данный header включен в несколько cpp файлов. Тогда при линковке произойдёт ошибка, т.к. функция имеет несколько определений. С inline функциями такой проблемы нет.

2) Если вы создаете inline функцию, то ее нужно размещать в header'e, так как

  • определение функции должно быть видно из того места в коде, где она используется;
  • если разделить определение и тело inline функции, то ее можно будет использовать только в том cpp файле, в котором находится ее тело.

Если определить метод внутри класса, то он автоматически становится inline:

class T {
    int size_;
public:
    size_t getSize() const { // inline!
        return size_;
    }
};

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

template <typename T>
    void swap (T & a, T & b) {
        ...
    }

Глобальные переменные

Что мы о них знаем? Что их не нужно использовать :), так как невозможно спрогнозировать, кто менял эту переменную, а значит и то значение, которое лежит в этой переменной. Если у нас все же есть глобальная переменная и она определена в нескольких cpp файлах, то наша программа упадет при линковке. Избежать этого позволяет ключевое слово extern:

// 1.cpp
int i = 0;

// 2.cpp
extern int i;    // extern указывает на то, что i определена где-то снаружи

На самом деле, перед любым объявлением функции автоматически ставится extern (можно явно писать, ошибки не будет).

Статические переменные

Если уж очень нужны глобальные переменные, лучше использовать статические переменные. Ключевое слово static обеспечивает защиту от перекрытия имен. Static может использоваться в 5 контекстах:

1. Статические глобальные переменные

// 1.cpp
static int i = 0;

Такое объявление переменной делает ее глобальной в пределах одного cpp файла, в данном случае - 1.cpp. Если объявить статическую переменную i в заголовочном файле, то в каждом файле, в который будет подключен этот header, будет по одной собственной i.

Замечание: типы линковки.

Линковка бывает:

  • external - функции и переменные между модулями;
  • internal - внутри одного модуля. Пример использования - счетчик открытых файлов.

2. Статические функции

Внутренняя линковка (internal).

Можно создавать разные функции с одинаковыми сигнатурами. Так как они определяются во время вызова, то могут и встроиться :).

Пример: можно в каждом cpp файле создать свою функцию static void test(). Напомню, что ключевое слово static защищает от конфликта имен.

3. Статические локальные переменные

Рассмотрим следующую функцию:

void print (double x) {
    static int i = 0;
    std::cout << (++i) << x << std::endl;
}

Переменная i хранится не на стеке. По сути, она является глобальной переменной, но ее областью видимости является только функция print.

Статические локальные переменные уничтожаются при выходе из main().

Если сделать функцию print статической и вставить в несколько cpp файлов, то у каждого print будет свой счетчик i и своя реализация. А если сделать ее inline, то 1 общая реализация.

Рассмотрим еще один пример:

1. void print (char const * fn, double x) {
2.     static int i = 0;
3.     static ofstream file(fn) = q(); // q() - некоторая функция
4.     file << (++i) << x << endl;
5. }
  • Если бы переменная ofstream file не была статической, то при каждом входе в print файл открывался бы заново.
  • Т.к. file - статическая, то при каждом входе в print, кроме первого раза, строка 3 игнорируется: как инициализация переменной file, так и =q().

Применение: хранение локальных данных функции.

char * g() {
    static char m[100];
    return m; // имеет смысл, т.к. m static => хранится не на стеке
}

Статические переменные так же можно возвращать по ссылке:

string & g() {
    static string s;
    return s;
}

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

  • невозможно проследить, кто и когда их изменяет. В многопоточных приложениях от них вообще стоит отказаться :)

Тем не менее, это эффективнее, чем выделение динамической памяти.

4. Статические члены данных класса

Вышесказанное относилось как к С++, так и к С. Этот и следующий параграфы, очевидно, относятся только к С++.

Рассмотрим пример:

class Array {
private:
    static int count_;   // объявление статического члена данных класса. Он не хранится в объекте класса.
    static string name_; // Статические члены данных класса подобны глобальным переменным внутри класса. 
};

В данном примере статические члены данных объявлены private, поэтому изменять их можно только из внутренних методов класса или "друзьями" (friend). Для того, чтобы проинициализировать статические члены данных, их нужно определить в каком-нибудь срр-файле, чтобы выделилась память:

Array::count_ = 0;
string Array::name_("Array");

Определить статическое поле прямо внутри класса можно тогда и только тогда, когда оно целочисленное и константное (т.к. в момент компиляции работа только с целочисленными типами):

static int const dimension = 10;

Примеры использования:

  • подсчет количества экземпляров класса;
  • хранение информации, общей для всех экземпляров класса;
  • хранение списка объектов класса.


5. Статические методы класса

Не зависят от объектов класса. Рассмотрим пример:

class Array {
private:
     static string name_;
public:
     static string getName() { // rem. getName() - inline, т.к. определена внутри класса 
         return name_;
     }
};

int main() {
    Array a;
    a.getName();      // cинтаксически возможно, но логически неправильно

    Array::getName(); // правильно
}

Метод getName() не имеет доступа к членам класса, но если передать в нее ссылку на объект класса, то из getName() можно будет изменять private поля.

Невозможно объявить статический метод класса константным, так как const запрещает изменять поля объекта, а в static методы this не передается:

static void f() const; // ошибка!

template и static

  • Поведение статических локальных переменных в шаблонах функций: для каждого инстанса функции будет своя статическая локальная переменная.
  • Static в шаблонах классов:
template <class T> 
    class Array {};

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

Но как определить снаружи статическую переменную шаблонного класса?

1 вариант:

// array.h
template <class T>
    class Array 
{
    static double q;
public:
    Array();
    static double getQ() { 
        return q;
    }
};

template <class T>
    double Array<T>::q = 1.5;

// main.cpp
#include <iostream>
#include "array.h"

int main()
{
    std::cout << Array<int>::getQ() << std::endl;
    std::cout << Array<float>::getQ() << std::endl;
    return 0;
}

//output: 1.5, 1.5

2 вариант:

// array.h
template <class T>
    class Array 
{
    static double q;
public:
    Array();
    static double getQ() { 
        return q;
    }
};

//array.cpp
#include "array.h"

template <>
        double Array<int>::q = 1.5;
template <>
        double Array<float>::q = 2.6;

// main.cpp
#include <iostream>
#include "array.h"

int main()
{
    std::cout << Array<int>::getQ() << std::endl;
    std::cout << Array<float>::getQ() << std::endl;
    return 0;
}

// output: 1.5, 2.6

NB. Вообще, шаблоны не очень дружат со static параметром шаблона: не может быть указателя на статическую функцию.

Синглтон

def. Синглтон - это объект, который в момент исполнения существует в единственном экземпляре.

Пример:

// array.h
struct Array {
private:
    // все конструкторы
    static Array * inst_;
public:
    static Array & getInstance() {
        if (inst_ == 0)
            inst_ = new Array();
        return *inst_;
    }
};

// array.cpp
Array * Array::inst_ = 0;