Немного предыстории.
Я довольно давно пишу игровой движок. Он разделен на несколько статических библиотек, таких как «utils», «rsbin» (система ресурсов), «window», которые затем связываются в один исполняемый файл.
Это кроссплатформенный движок, компилируемый для Windows и Android. Под Windows я компилирую его с помощью MinGW. Под Android с помощью CCTools, который является интерфейсом к нативному gcc.
Одним из базовых классов является utils::RefObject
, который представляет концепцию, аналогичную IUnknown в Windows: он предоставляет счетчик ссылок для определения времени жизни и метод для запроса определенного интерфейса из указателя базового класса. Также есть template< typename T > utils::Ref
, созданный специально для таких объектов. Он содержит std::atomic< utils::RefObject* >
и автоматически обновляет счетчик ссылок своего объекта при построении, назначении и уничтожении аналогично std::shared_ptr
. Это также позволяет неявно преобразовывать RefObjects разных типов с помощью их методов запроса. Однако неэффективно запрашивать объект для его собственного типа, поэтому utils::Ref
перегружает большинство своих операторов, например. грамм. есть специальный конструктор utils::Ref< T >::Ref( T* ptr )
, который просто увеличивает количество ссылок переданного объекта, и общий utils::Ref< T >::Ref( RefObject* ptr )
, который запрашивает свой аргумент для экземпляра T и выдает исключение в случае неудачи (хотя не волнуйтесь, конечно, есть метод для мягкого приведения).
Но наличие только этих двух методов создает проблему: вы не можете явно инициализировать utils::Ref
нулевым указателем, так как это неоднозначно; так что есть также utils::Ref< T >::Ref( nullptr_t )
, чтобы предоставить способ сделать это.
Теперь мы подходим к проблеме. В заголовочном файле прототип пишется точно так же, как указано выше, без предшествующего std::
. Обратите внимание, что я также не использую using namespace
. Долгое время это работало.
Сейчас я работаю над графической системой. Он существовал и раньше, но был довольно рудиментарным, поэтому я даже не заметил, что ‹gl.h› на самом деле определяет только OpenGL 1.1, а для более новых версий нужно использовать ‹glext.h›. Теперь возникла необходимость использовать последний. Но в том числе и это сломало старый эталонный класс.
Судя по сообщениям об ошибках, у MinGW теперь есть проблемы с этим nullptr_t
в прототипах. Я провел быстрый поиск в Интернете и обнаружил, что часто его называют std::nullptr_t
. Хотя, не везде.
Подводя итог: у меня было nullptr_t
без std::
или using namespace
компиляции, пока я не включил ‹glext.h› перед заголовком.
Сайт cplusplus.com/reference, который я использовал до сих пор, предполагает, что глобальный ::nullptr_t
как должно быть. С другой стороны, вики en.cppreference.com сообщает, что на самом деле это std::nullptr_t
.
Быстрая тестовая программа, helloworld с void foo( int )
и void foo( nullptr_t )
, не скомпилировалась, и теперь причина явно "error: 'nullptr_t' was not declared in this scope"
с предложением использовать вместо нее std::nullptr_t
.
Нетрудно будет добавить std::
там, где это необходимо; но этот случай оставил меня довольно любопытным.
cplusplus.com на самом деле лгал? => Ответил в кометах, да. Это неточный источник.
Тогда, если nullptr_t
действительно находится в namespace std
, почему utils::Ref
компилируется? => С предложениями в комментариях, провел пару тестов и обнаружил, что ‹mutex›, включенный в какой-то другой заголовок, при размещении перед любым заголовком stddef определяет глобальный ::nullptr_t
. Конечно, не идеальное поведение, но это не серьезная ошибка. Вероятно, в любом случае следует сообщить об этом разработчикам MinGW/GCC.
Почему включение ‹glext.h› ломает его? => Когда какой-либо заголовок stddef включен перед ‹mutex›, тип определяется в соответствии со стандартом как std::nullptr_t
. ‹glext.h› включает в себя ‹windows.h›, который, в свою очередь, обязательно включает заголовок stddef, а также целый пакет других необходимых для WinAPI.
Вот источники, определяющие рассматриваемый класс:
- utils/ref.hpp
- utils/ref.cpp
- utils/refobject.hpp
- utils/refobject.cpp
- utils/logger.hpp => Здесь используется мьютекс, чтобы избежать разрыва строки во время вывода.
- utils/cbase.hpp
(последние 2 включены и поэтому тоже могут повлиять)
Как было предложено в комментариях, я запустил g++ -E в тестовом случае, который скомпилировался, и нашел довольно интересный фрагмент в ‹stddef.h›:
#if defined(__cplusplus) && __cplusplus >= 201103L
#ifndef _GXX_NULLPTR_T
#define _GXX_NULLPTR_T
typedef decltype(nullptr) nullptr_t;
#endif
#endif /* C++11. */
Теперь, чтобы найти, где _GXX_NULLPTR_T
определяется еще... быстрый GREP через файлы MinGW не нашел ничего, кроме этого stddef.h
Так что до сих пор остается загадкой, почему и как он отключается. Особенно, когда включение только ‹stddef.h› и ничего больше нигде не определяет nullptr_t
, несмотря на чуток выше.
::nullptr_t
. - person tenfour   schedule 24.02.2015using ::std::nullptr_t;
в заголовке. Как MSVC++. - person Hans Passant   schedule 24.02.2015<stddef.h>
где-либо и полагаетесь на какой-то другой заголовок, включающий его от вашего имени. Это ненадежно и не всегда работает. Если вы хотите что-то определенное в этом заголовке, включите этот заголовок. - person   schedule 24.02.2015nullptr_t
используется для перегрузки функций, принимая различные типы указателей, так как в противном случае их вызов с нулевым указателем потребовал бы его явного приведения к одному из принятых типов. Когда у вас естьvoid foo( int* )
иvoid foo( double* )
, вызовfoo( 0 )
неоднозначен: компилятор не знает, какую функцию вызывать. Мы можем привести к типу ноль, чтобы указать, какой foo нам нужен:foo( ( double* )0 )
будет преобразован в последний. Но C++11 дает нам более элегантный способ сделать это, введя особый случайvoid foo( nullptr_t )
... - person Delfigamer   schedule 24.02.2015foo( nullptr )
, который однозначно разрешит свою собственную функцию. Вutils::Ref
, описанном в OP, этот синтаксис используется для явной инициализации ссылки с нулевым указателем и для ее назначения, когда нам больше не нужен объект, который он удерживал, и предоставление нового не имеет смысла. - person Delfigamer   schedule 24.02.2015nullptr_t
, так как я не вижу, чем он отличается отbool
настолько, чтобыbool
,false
иtrue
были встроенными, а тип встроенногоnullptr
- это какое-то объявление в заголовочном файле C. Логичнее было бы либо ввести письменный классstd::nullptr_t
сnullptr_t const nullptr
(что вполне возможно в C++), либо сделать их оба ключевыми словами. Что касается замыканий и, аналогично, списков инициализации, то они являются достаточно сложными понятиями, чтобы быть непредставимыми с элементарными типами, поэтому других способов их реализации не так много. - person Delfigamer   schedule 24.02.2015bool
— это зарезервированное слово в C++. Добавление новых зарезервированных слов может привести к нарушению обратной совместимости. Итак, за исключением веских причин, это не делается. - person Yakk - Adam Nevraumont   schedule 25.02.2015std::function
имеет очень мало общего с лямбда-выражениями.std::function
— это общий функтор, который может содержать все, что может быть вызвано, и должен использовать стирание типа для достижения этого (с соответствующими затратами времени выполнения). Тип лямбда-выражения — это уникальный безымянный класс, тип замыкания. - person Angew is no longer proud of SO   schedule 25.02.2015std::function
. - person Angew is no longer proud of SO   schedule 25.02.2015