Как исправить предполагаемое отсутствие явного объявления экземпляра при компиляции CRTP Singleton с Clang?

Мы используем любопытно повторяющийся шаблон шаблона для реализации синглтонов. . Однако в последних версиях Clang мы получаем предупреждение -Wundefined-var-template. Предлагаемое исправление заключается в добавлении «явного объявления создания экземпляра».

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

Какая подходящая конструкция для устранения проблемы, отмеченной этим предупреждением?


Упрощенные детали (большая часть логики была удалена для создания MCVE):

SingletonBase.hh:

template < class T > class SingletonBase {
public:
  static T * get_instance() {
    if ( ! instance_ ) {
      instance_ = T::create_singleton_instance();
    }
    return instance_;
  }
private:
  static T * instance_;
};

Синглтон.хх:

#include "SingletonBase.hh"

class Singleton : public SingletonBase< Singleton > {
  friend class SingletonBase< Singleton >;
public:
  int do_stuff(int v) { return v+2; }
private:
  static Singleton * create_singleton_instance() {
    return new Singleton;
  }
};

Singleton.cc:

#include "Singleton.hh"
template <> Singleton * SingletonBase< Singleton >::instance_( nullptr );

Когда мы компилируем с использованием последней версии clang (3.9.0; но не с clang 3.7), мы получаем предупреждение при компиляции файлов, отличных от Singleton.cc. (с -std = c ++ 11 и -Werror)

In file included from OtherFile.cc:2:
In file included from ./Singleton.hh:2:
./SingletonBase.hh:5:16: warning: instantiation of variable 'SingletonBase<Singleton>::instance_' required here, but no definition is available [-Wundefined-var-template]
        if ( ! instance_ ) {
               ^
OtherFile.cc:5:25: note: in instantiation of member function 'SingletonBase<Singleton>::get_instance' requested here
      return Singleton::get_instance()->do_stuff(4);
                        ^
./SingletonBase.hh:11:18: note: forward declaration of template entity  is here
       static T * instance_;
             ^

./SingletonBase.hh:5:16: note: add an explicit instantiation declaration to suppress this warning if 'SingletonBase<Singleton>::instance_' is explicitly instantiated in another translation unit
        if ( ! instance_ ) {
               ^
1 error generated.

Я добавил следующую строку в конец Singleton.hh, так как это то, к чему я приводит полагаю, явный синтаксис объявления экземпляра должен быть.

extern template Singleton* SingletonBase< class Singleton >::instance_;

Хотя это устраняет проблемы с компиляцией OtherFile.cc, это приводит к новой ошибке при компиляции Singleton.cc.

Singleton.cc:3:57: error: explicit specialization of 'instance_' after instantiation
    template <> Singleton * SingletonBase< Singleton >::instance_( nullptr );
                                                    ^
./Singleton.hh:14:66: note: explicit instantiation first required here
    extern template Singleton* SingletonBase< class Singleton >::instance_;
                                                             ^
1 error generated.

Что мне здесь делать, чтобы исправить эти предупреждения / ошибки? Есть ли более подходящий синтаксис для явного объявления экземпляра, который я не понимаю?


person R.M.    schedule 09.09.2016    source источник
comment
Но почему, ну почему? Вы создали статический член var, а не блокировку, поэтому он не является поточно-ориентированным. Вы можете просто поместить эту статику в get_instance(), сэкономить if, сделать его потокобезопасным. Ваш базовый класс не гарантирует, что это синглтон, но для него требуется функция create_instance. Я могу в любой момент создать два экземпляра Singleton, даже не касаясь вашего интерфейса. Вы не можете разрушить-воссоздать его. Вы можете иметь базовые и дочерние синглтоны одновременно. Вы протекаете и никогда не вызываете деструктор. Исправление? Удалите эти источники и начните заново. (Без обид, мы поможем вам выбрать подходящий)   -  person lorro    schedule 10.09.2016
comment
@Iorro Имейте в виду, что я вырезал большую часть деталей (включая биты безопасности потоков), чтобы дать вам MCVE. Если у вас есть реализация Singleton, которую вы считаете передовой, не стесняйтесь ссылаться на нее в комментариях, но мой исходный вопрос все еще остается в силе.   -  person R.M.    schedule 10.09.2016
comment
Это очень важно, я предлагаю вам добавить комментарий по этому поводу в разделе основного вопроса, иначе люди попытаются исправить это, а не ошибку компилятора. Работает ли у вас ›переменная-член - исправление статической переменной функции, или у вас есть причины оставить ее членом?   -  person lorro    schedule 10.09.2016


Ответы (3)


Вместо этого я бы порекомендовал эту реализацию синглтона:

template<class T>
struct SingletonBase {
    static T& get_instance() {
        static T instance;
        return instance;
    }
};

Это потокобезопасный и удалите ваше предупреждение.

Если хотите, можете оставить себе create_singleton_instance:

template<class T>
struct SingletonBase {
    static T& get_instance() {
        static T instance{T::create_singleton_instance()};
        return instance;
    }
};

И изменив реализацию функции на:

static SomeClass create_singleton_instance() {
    return {};
}
person Guillaume Racicot    schedule 10.09.2016
comment
.. кроме того, что это не _ single_ton. На данный момент у вас нет гарантии ни на один экземпляр. - person lorro; 10.09.2016
comment
Затем просто сделайте конструктор закрытым и добавьте его в друзья. - person Guillaume Racicot; 10.09.2016
comment
Предлагаемое решение ломает что-то ужасное в Microsoft Windows 8 и ниже с Visual Studio 2015 и ниже. Microsoft не предоставляет магическую статику, известную как N2660 Динамическая инициализация и уничтожение с параллелизмом. Смена компилятора не исправляет. Проблема в среде выполнения и платформе. Это основная языковая функция, на создание которой потребовалось почти десять лет. Microsoft превратила дефект в оружие и использовала его, чтобы заставить людей перейти на Windows 10, чтобы получить его. - person jww; 16.08.2017
comment
@jww это прискорбно. Однако я не буду писать неоптимальное решение, потому что платформа имеет дефекты и отсутствие packport. Это по-прежнему лучшее решение для синглтона и, скорее всего, так и останется. Однако я бы написал записку в OP, если бы она была помечена msvc или чем-то в этом роде. Поскольку он помечен как clang++, проблема с этой идиомой неизвестна. - person Guillaume Racicot; 16.08.2017

Самое простое исправление - определить instance_ в SingletonBase.hh:

template < class T > class SingletonBase {
public:
  static T * get_instance() {
    if ( ! instance_ ) {
      instance_ = T::create_singleton_instance();
    }
    return instance_;
  }
private:
  static T * instance_;
};

template <typename T>
T* SingletonBase<T>::instance_ = nullptr;

Однако я не вижу смысла SingletonBase, если вы собираетесь полагаться на T::create_singleton_instance() при создании экземпляра. Вы также можете реализовать get_instance() в производном классе.

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

template < class T > class SingletonBase {
   public:
      static T& get_instance() {
         static T instance_;
         return instance_;
      }
   private:
};

Дополнительная литература: Как реализовать многопоточный безопасный синглтон в C ++ 11 без использования ‹mutex›

person R Sahu    schedule 10.09.2016

По-видимому, явное объявление экземпляра должно иметь форму

template <> Singleton * SingletonBase< Singleton >::instance_;
person skimo    schedule 13.08.2019