CRTP Singleton Неполный тип или Нелитеральный тип - PullRequest
2 голосов
/ 14 февраля 2020

Я пытаюсь сделать CRTP Singleton. Здесь уже есть пара примеров. Я не уверен, как мой отличается или почему он не компилируется. Первая попытка:

template<class Impl>
class Base
{
public:
  static const Impl& getInstance();
  static int foo(int x);
private:
  static const Impl impl{};
};
template<class Impl> inline
const Impl& Base<Impl>::getInstance()
{
  return impl;
}
template<class Impl> inline
int Base<Impl>::foo(int x)
{
  return impl.foo_impl(x);
}

class Derived1 : public Base<Derived1>
{
public:
  int foo_impl(int x) const;
};
int Derived1::foo_impl(int x) const
{
  return x + 3;
}

int main(int argc, char** argv)
{
  const Derived1& d = Derived1::getInstance();
  std::cout << Derived1::foo(3) << std::endl;
  return 0;
}

g ++ 7.4.0 говорит мне: error: in-class initialization of static data member ‘const Derived1 Base<Derived1>::impl’ of incomplete type.

Хорошо. Хорошо, тогда. Не уверен, почему этот тип не завершен. Попробуйте:

 . . .
 private:
   static constexpr Impl impl{};
 };

Теперь мы терпим неудачу во время ссылки: undefined reference to 'Base<Derived1>::impl' Действительно ?! Выглядит определенным и инициализированным для меня ... Но даже если бы он связал меня, я получил Derived с нетривиальным деструктором, так что компилятор собирается бомбить во время компиляции, жалуясь на не буквальный тип, используемый в constexpr.

Почему Derived1 не завершен? Как я могу построить это?

Ответы [ 3 ]

2 голосов
/ 14 февраля 2020

Неполная ошибка типа возникает из-за того, что вы используете impl в getInstance до ее появления.

Один из способов исправить это - инициализировать impl вне определения класса и убедиться, что он инициализирован перед использованием:

template <class Impl>
const Impl Base<Impl>::impl {};
1 голос
/ 14 февраля 2020

К моменту времени, когда создается экземпляр Base<Derived1> (в самом начале определения Derived1), класс Derived1 является неполным, поскольку он является тем, который до конца своего объявления. Действительно, невозможно иметь полный тип в CRTP, поскольку производный тип никогда не будет завершенным, пока вы не объявите его наследование.

Для нестатических c членов данных единственный способ обойти это - использовать какой-то указатель на неполный тип (наиболее вероятно, std::unique_ptr). Для членов stati c это также работает, но может также просто разделить объявление и определение члена stati c. Поэтому вместо

template<Impl>
struct Base {
   static Impl impl{};
};

напишите

template<Impl>
struct Base {
    static Impl impl;
};

и определите его следующим образом

template<Impl>
static Base<Impl>::impl ={};

после завершения Derived1. (Обратите внимание, что я не уверен, как это работает для частных пользователей c). На мой взгляд, было бы лучше, если бы каждая реализация делала это для себя сама, то есть после завершения Derived1 добавьте

template<>
static Base<Derived1>::impl = {};

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

1 голос
/ 14 февраля 2020

Попробуйте реализовать функцию getInstance следующим образом:

template <class Impl>
inline const Impl& Base<Impl>::getInstance() {
    static const Impl impl{};
    return impl;
}

и затем в foo функции

template <class Impl>
inline int Base<Impl>::foo(int x) {
    return getInstance().foo_impl(x);
}

Демо

...