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

StackOverflow https://stackoverflow.com/questions/607259

  •  03-07-2019
  •  | 
  •  

Вопрос

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

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

Это было полезно?

Решение

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

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

Другие советы

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top