Использование статической переменной вместе с шаблонами

У меня есть класс шаблона, определенный в файле заголовка вроде этого. Здесь я также определил статическую переменную:

#ifndef TEST1_H_
#define TEST1_H_

void f1();

static int count;

template <class T>
class MyClass
{
public:

    void f()
    {
        ++count;
    }


};

#endif

И я определил функцию main() в другом файле cpp следующим образом:

int main(int argc, char* argv[])
{
    MyClass<int> a;
    a.f();
    f1();

    cout<<"Main:" << count << "\n";

    return 0;
}

Я реализовал функцию f1() в другом файле cpp следующим образом:

void f1()
{
    MyClass<int> a;
    a.f();

    cout<<"F1: " <<count <<"\n";
}

Когда я скомпилировал это с помощью VC6, я получил вывод как "F1: 0 Main: 2". Как это возможно? Кроме того, в целом, как я должен обрабатывать, если я хочу использовать статические переменные вместе с шаблонами?

Ответ 1

Вы получаете две копии одной и той же переменной, потому что вы объявили статическую переменную в файле заголовка. Когда вы объявляете глобальную переменную static таким образом, вы говорите ее локально компилятору (файл .o). Поскольку вы включаете заголовок в два блока компиляции, вы получаете две копии count.

Я думаю, что вам действительно нужна статическая переменная-член шаблона, связанная с каждым экземпляром класса шаблона. Это будет выглядеть так:

template <class T>
class MyClass
{
    // static member declaration
    static int count;
    ...
};

// static member definition
template<class T> int MyClass<T>::count = 0;

Это даст вам счет для каждого экземпляра вашего шаблона. То есть у вас будет счетчик для MyClass<int>, MyClass<foo>, MyClass<bar> и т.д. f1() теперь будет выглядеть следующим образом:

void f1() {
    MyClass<int> a;
    a.f();

    cout<<"F1: " << MyClass<int>::count <<"\n";
}

Если вам требуется подсчет для всех экземпляров MyClass (независимо от их параметров шаблона), вам нужно использовать глобальную переменную .

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

int& my_count() {
    static int count = 0;
    return count;
}

Затем вы получите доступ к нему из вашего класса следующим образом:

void f() {
    ++my_count();
}

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

Ответ 2

Помещение статического объявления в файл заголовка приведет к тому, что каждый .cpp файл получит свою собственную версию переменной. Таким образом, два оператора cout печатают разные переменные.

Ответ 3

Вы ожидали "F1:1 Main: 1"? Вы создали экземпляр MyClass<int> в двух отдельных единицах перевода (т.е. В двух объектных файлах), и компоновщик увидел, что существует дублирующее создание шаблона, поэтому оно отбросило экземпляр, который был в объекте f1.

Вы передаете /OPT:ICF или /OPT:REF в компоновщик VC6? Это может быть связано с удалением дубликатов шаблонов (или нет, дублирование экземпляров шаблонов может быть частным случаем по сравнению с обычными дублирующими функциями). GCC, похоже, делает нечто подобное на некоторых платформах.

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

Ответ 4

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

class Parent
{
protected: 
    static long count;
};

long Parent::count = 0;

template<typename T>
class TemplateClass: private Parent
{
private: 
    int mKey;
public:
    TemplateClass():mKey(count++){}
    long getKey(){return mKey;}
}

int main()
{
    TemplateClass<int> obj1;
    TemplateClass<double> obj2;

    std::cout<<"Object 1 key is: "<<obj1.getKey()<<std::endl;
    std::cout<<"Object 2 key is: "<<obj2.getKey()<<std::endl;

    return 0;
}

Выход будет:

Object 1 key is: 0 
Object 2 key is: 1

Ответ 5

Я думаю, что это на самом деле поведение undefined.

В соответствии с С++ 14 [basic.def.odr]/6:

В программе может быть несколько определений [...] функции-члена шаблона класса [...], при условии, что каждое определение появляется в другой единицы перевода и при условии, что определения удовлетворяют следующим требованиям, Учитывая такой объект с именем D, определенный в нескольких единицах перевода, тогда

  • каждое определение D должно состоять из одной и той же последовательности токенов; и
  • в каждом определении D соответствующие имена, просмотренные в соответствии с 3.4, относятся к сущности, определенной в определении D, или должны ссылаться на один и тот же объект после разрешения перегрузки (13.3) и после согласования частичного шаблона специализация (14.8.3), за исключением того, что имя может ссылаться на энергонезависимое const с внутренней или никакой привязкой, если объект имеет одинаковый тип литерала во всех определениях D, и объект инициализируется константным выражением (5.19), и объект не используется odr, и объект имеет то же значение во всех определениях D; [...]

Проблема заключается в том, что в первом файле .cpp имя count внутри f1 относится к другому объекту, чем имя count внутри f1 во втором файле .cpp, нарушая тем самым что соответствующие имена должны относиться к одному и тому же объекту.

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