С++ шаблонная странная оптимизация

Я написал шаблон шаблона singleton, как и boost.

template <typename _T>
class Singleton
{ 
    public :
    static _T* Instance()
    {
        static _T obj;
        return &obj;
    }

protected :
    Singleton() {}

private :
    struct ObjectCreator
    {
        ObjectCreator()
        {
            Singleton<_T>::instance();
        }
    };

    static ObjectCreator object_creator;
};

template <typename _T>
typename Singleton<_T>::ObjectCreator Singleton<_T>::object_creator;

И я написал основную функцию для ее проверки.

#include "Singleton.h"
class A : public Singleton <A>
{
    public:
        int a;
};


int main()
{
    A::Instance()->a = 2;
}

Я знаю, что туманный экземпляр в ObjectCreator конструкторе, странно, я могу скомпилировать его правильно gcc-4.4.7, затем я использовал clang-6.0, он ударил меня опечаткой.

Я думаю, gcc может сделать некоторую оптимизацию, потому что я ничего не делал с ObjectCreator, поэтому он проигнорировал код ошибки.

У меня есть два вопроса:

  1. Что я должен сделать, чтобы gcc сообщила мне об этой ошибке (без изменения моего кода), например, добавить флаг компилятора?
  2. Если у кого-то есть более надежное объяснение этому? Некоторые официальные документы будут делать.

Ps: Я знаю, что boost добавит do_nothing в ObjectCreate и вызовет его из Singleton<_T>:: Instance() чтобы избежать этой оптимизации.

Ответ 1

  • Что делать, чтобы gcc сообщал об этой ошибке (без изменения моего кода), например, добавить флаг компилятора?

Вы можете добавить явный template class Singleton<float>; экземпляра template class Singleton<float>; (Я просто случайно выбрал float как тип, но вы могли выбрать что-нибудь более подходящее), чтобы заставить GCC проверить синтаксис. См. Https://gcc.godbolt.org/z/ii43qX для примера.

Если вы просто хотите проверить, вы также можете поместить это явное instanciation в отдельный блок компиляции, добавив еще один cpp файл в ваш проект.

Тем не менее, выполнение явного instanciation сильнее, чем неявное instanciation, поскольку все члены и методы будут instanciated. Такое поведение может быть нежелательным (см. Стандартную библиотеку для примеров).

  • Если у кого-то есть более надежное объяснение этому? Некоторые официальные документы будут делать.

Статические члены не инициализируются неявно до тех пор, пока они не будут использоваться таким образом, чтобы его определение требовалось (это сильно отличается от явного instanciation).

@StoryTeller нашел правильный параграф в стандарте

14.7.1 Неявное создание экземпляра [temp.inst]

Неявное создание экземпляра специализации шаблона вызывает неявное создание объявлений, но не определений или аргументов по умолчанию, функций-членов класса, классов-членов, статических элементов данных и шаблонов-членов; и это вызывает неявное создание определений членских анонимных союзов. Если член шаблона класса или шаблон-член явно не создавался или не был явно специализирован, специализация члена неявно создается, когда специализация ссылается в контексте, который требует определения члена; в частности, инициализация (и любые связанные с ней побочные эффекты) статического члена данных не происходит, если сам статический член данных не используется так, чтобы требовалось определение статического члена данных.

EDIT. Вы должны принять ответ @StoryTeller, поскольку он правильно объяснил оба аспекта вашего вопроса.

Ответ 2

Если я правильно прочитал стандарт, я не верю, что ваш код плохо сформирован (запретите использование идентификатора _T). Кланг, идущий на лишнюю милю, фантастичен, но GCC не ошибается, чтобы принять его как есть.

Причина в том, что ваша программа содержит только неявное создание вашего шаблона. Согласно N1905 1 (акцент мой):

14.7.1 Неявное создание экземпляра [temp.inst]

1... Неявное создание экземпляра специализации шаблона вызывает неявное создание объявлений, но не определений или аргументов по умолчанию, функций- членов класса, классов-членов, статических элементов данных и шаблонов-членов; и это вызывает неявное создание определений членских анонимных союзов. Если член шаблона класса или шаблон-член явно не создавался или не был явно специализирован, специализация члена неявно создается, когда специализация ссылается в контексте, который требует определения члена; в частности, инициализация (и любые связанные с ней побочные эффекты) статического члена данных не происходит, если сам статический член данных не используется так, чтобы требовалось определение статического члена данных.

Ничто не использует object_creator таким образом, чтобы его определение существовало. Таким образом, только декларация проверяется. Кроме того, требуется только создание объявления class ObjectCreator, а не его определение (или определение его конструктора). По той же причине можно определить внешнюю переменную типа объявленного класса вперед:

extern class C c;

В приведенном выше абзаце (и object_creator не используется object_creator) требуется только object_creator имени типа и имени объекта, чтобы создать эффект, аналогичный вышеупомянутой внешней декларации.

В результате GCC никогда не должен проверять, что instance действителен. Я бы сказал, что, учитывая вышеприведенный параграф, даже если у вас не было опечатки, вполне возможно, что object_creator не делает то, что вы думаете. Если ваш код работал, то только потому, что функция local static obj была инициализирована при первом использовании (что делает ObjectCreator избыточным).

Что касается того, почему добавление явного экземпляра (например, предложенное @P i) немедленно вызывает ошибку. Мы можем видеть здесь:

14.7.2 Явное создание экземпляра [temp.explicit]

7 Явное инстанцирование специализации шаблона класса также явно создает экземпляр каждого из его членов (не включая членов, унаследованных от базовых классов), определение которых видимо в момент создания экземпляра и которое ранее не было явно специализировано в блоке перевода, содержащем явное инстанцирование,

Когда мы это делаем, мы рекурсивно вынуждаем все, чтобы быть созданными, и, как результат, проверены.


1 - Это проект 2005 года.Очень близко к С++ 03, поэтому я считаю целесообразным использование GCC 4.4.7.