Глобально вызываемый конструктор шаблона класса, который добавляет этот указатель в список stati c, вызывает ошибку - PullRequest
0 голосов
/ 07 марта 2020

У меня есть шаблон класса, который отслеживает все экземпляры указанного c экземпляра шаблона класса со списком указателей. В конструкторе я push_back(this) добавляю созданный экземпляр в класс. Если я создаю экземпляр в main, он работает нормально. Но если я создаю глобальный экземпляр, конструктор вызывает ошибку. Ошибка от list.push_back() и говорит, что итератор вставки находится вне диапазона контейнера. Я воссоздал ошибку с минимальным кодом.

Заголовочный файл

#ifndef HEADER_H
#define HEADER_H

#include <iostream>
#include <list>

template<typename T>
class A
{
public:
    static std::list<A<T>*> l;

    A( T t ) : val( t ) { l.push_back( this ); }
    ~A() { l.remove( this ); }

    T val;

    void print() { std::cout << val;  }
};

template<typename T>
std::list<A<T>*> A<T>::l;

#endif

Исходный файл

#include "Header.h"

A<int> temp( 0 );

void main()
{
    temp.print();

    char c;
    std::cin >> c;
}

Я предполагаю, что это как-то связано с вызовом конструктора порядок, но кто-нибудь знает, как это исправить?

Ответы [ 2 ]

3 голосов
/ 07 марта 2020

Stati c члены специализаций шаблонных классов имеют неупорядоченную dynamici c инициализацию, что означает, что нет никакой гарантии, в каком порядке они будут инициализированы относительно других динамических c инициализаций.

Если A<int>::l инициализируется после temp, то при инициализации temp будет пытаться получить доступ к A<int>::l до того, как началось его время жизни, что приведет к неопределенному поведению.

Обычным решением этого является поместите переменную stati c в функцию stati c и вызовите ее вместо этого. Локальные переменные stati c инициализируются, когда выполнение достигает своего объявления в первый раз, поэтому они всегда правильно упорядочены.

template<typename T>
class A
{
public:
    static auto& l() {
        static std::list<A<T>*> instance;
        return instance;
    }

    A( T t ) : val( t ) { l().push_back( this ); }
    ~A() { l().remove( this ); }

    T val;

    void print() { std::cout << val;  }
};

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

Я не знаю, какова цель списка экземпляров здесь, но у него есть похожие проблемы, и его, вероятно, следует избегать, если возможный. Кроме того, он будет иметь довольно плохую производительность, особенно если у вас много экземпляров A<T>, потому что l.remove( this ); будет занимать линейное время в количестве элементов в списке. Попробуйте использовать std::set или std::unordered_set вместо std::list.


Дополнительные примечания:


Тип возврата main must быть int в C ++. void, поскольку возвращаемый тип является нестандартным расширением некоторых компиляторов.


Ваш класс нарушает правило 0/3/5 . Вам нужно определить конструктор копирования и перемещения и оператор присваивания с правильной семантикой, иначе ваш список экземпляров не будет содержать все экземпляры, когда ваш класс копируется.


Возможно, вы также захотите сохранить const A<T>* вместо A<T>* в списке. Последнее опасно. Если вы когда-нибудь объявите переменную типа const A<T>, то указатель, который вы сохраните в списке, все равно будет не const. Если вы затем используете указатель из списка для изменения объекта, вы не будете предупреждены о том, что пытаетесь изменить объект const (что вызывает неопределенное поведение).

0 голосов
/ 15 марта 2020

Использование переменной block scoped stati c (переменная local static в функции / функции c) является очень хорошим подходом, поскольку инициализация class template static data members и других переменных static/thread-local равна indeterminately sequenced относительно друг к другу, и это вызывает проблему доступа к переменной, которая еще не инициализирована. Узнайте больше об этом на Stati c нелокальная инициализация Но этот подход не позволит использовать A<T>::l, потому что тогда переменная std::list<A<T>*> l не является static member класса A<T>, это означает, что если нельзя избежать static member класса из-за некоторых требований, то это неупорядоченная проблема инициализации должна быть решена.

Теперь unordered инициализация может быть преобразована в ordered инициализацию с помощью explicit template initialization, как показано ниже:

template class A<int>; //explicit template initialization

A<int> temp(1);

Это обеспечит инициализацию std::list<A<T>*> l до temp инициализация объекта. Эта явная инициализация шаблона должна выполняться в каждой единице перевода, в которой вы хотите создать non-local переменную A<T>. Давайте посмотрим на код,

Заголовочный файл похож (без изменений),

Исходный файл

#include "Header.h"

template class A<int>; //explicit template initialization

A<int> temp(0);

int main(int , char *[]){
    std::cout<< "temp.val = ";

    temp.print();

    std::cout<< '\n';
}
...