Использование статической переменной вместе с шаблонами - PullRequest
18 голосов
/ 03 марта 2009

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

#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". Как это возможно? Кроме того, в целом, как мне поступить, если я хочу использовать статические переменные вместе с шаблонами?

Ответы [ 5 ]

22 голосов
/ 03 марта 2009

Вы получаете две копии одной и той же переменной, потому что вы объявили статическую переменную в заголовочном файле. Когда вы объявляете глобальную переменную 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();
}

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

3 голосов
/ 03 марта 2009

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

1 голос
/ 04 марта 2009

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

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

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

0 голосов
/ 18 декабря 2016

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

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
0 голосов
/ 16 ноября 2015

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

Согласно C ++ 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, который говорит, что каждая единица перевода получает свой собственный объект с таким именем.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...