Как проверить, объявлен ли класс в C++?
Я пишу часть программного обеспечения вокруг фреймворка, и класс, который я использую (если быть точным, расширяющим), был переименован в более позднюю версию. Есть ли какой-нибудь способ написать несколько макросов/шаблонов в С++ 11, чтобы определить, был ли класс с определенным именем объявлен в точке кода?
Иллюстрация того, чего я пытаюсь достичь, приведена ниже. Предположим, что файл class_include.h содержит определение класса A
:
class A
{
...
};
или класс B
:
class B
{
...
};
и класс C
пытается расширить то, что объявлено:
#include <class_include.h>
#if (class A is declared)
class C : public A
#else // class B is declared
class C : public B
#endif
{
...
};
Примечание: мне пришло в голову попробовать проверить версию фреймворка, но ответ на этот вопрос меня интересует. Я также не могу изменить какие-либо рамки заголовочных файлов.
РЕДАКТИРОВАТЬ: принятый ответ зависит от того, определен ли класс (что подразумевает объявление), и, в моем случае, класс объявляется, если он определен.
Ответы
Ответ 1
Вы можете и без макросов требуется. Во-первых, вы можете "переслать" объявление класса даже после того, как станет доступно его полное определение. Т.е. это действительно:
class foo{};
class foo;
Теперь с помощью void_t
реализации void_t
и is_complete
типа is_complete
вы можете сделать что-то вроде этого:
#include <type_traits>
template<typename... Ts> struct make_void { typedef void type;};
template<typename... Ts> using void_t = typename make_void<Ts...>::type;
template <typename T, typename Enabler = void>
struct is_complete : std::false_type {};
template <typename T>
struct is_complete<T, ::void_t<decltype(sizeof(T) != 0)>> : std::true_type {};
class A;
class B;
class C : public std::conditional<is_complete<A>::value, A, B>::type {
};
В зависимости от того, присутствует или нет полное определение A
, C
будет наследоваться от A
или B
публично. Смотрите живой пример.
Но я предупреждаю, с этим нужно обращаться осторожно, иначе у вас, скорее всего, будет нарушение ODR в вашей программе.
Ответ 2
В дополнение к уже изложенным магическим идеям шаблона традиционный подход заключается в использовании макросов "версия" библиотеки, если это возможно. Если их нет, не могли бы вы просто изменить свой код и начать использовать новую версию библиотеки? Обозначьте новую версию зависимости в вашей системе сборки соответствующим образом.
В конечном счете, контроль зависимостей является нормальной и ожидаемой частью процесса развертывания программного обеспечения. Так что, хотя это может быть немного неприятно, я бы не стал слишком усложнять ваш код, пытаясь полностью его устранить. Я имею в виду, что вы уже должны перечислить библиотеку в той или иной форме как зависимость, так что вы уже на полпути, даже не начав!
Другие ответы технически достигают вашей цели, поскольку мы предполагаем, что "класс A определен" можно считать эквивалентным "класс A определен и принимает форму именно того, что, по нашему мнению, должно принимать форму". Без рабочего контроля зависимостей вы уже как бы облажались, и с ним вам не нужны хаки.
Ответ 3
Одним из способов является использование SFINAE с использованием typeid
который будет отличаться от результата неполного типа:
#include <iostream>
#include <typeinfo> // for typeid
template<typename T, typename = void>
constexpr bool is_defined = false;
template<typename T>
constexpr bool is_defined<T, decltype(typeid(T), void())> = true;
struct complete {}; // i.e. 'complete' is defined.
struct incomplete; // not defined, just a forward declaration
int main()
{
std::cout << is_defined<complete> << " " << is_defined<incomplete>;
}
Это требует, чтобы вы объявили классы вперёд, но, поскольку is_defined
- это constexpr
его можно использовать во время компиляции. Вы также можете использовать sizeof
но я нервничаю по поводу пустых оптимизаций базового класса, дающих ложные срабатывания.
Ответ 4
В вашем случае, поскольку вы хотите наследовать от класса, он должен быть объявлен, но также и определен; и это немного упрощает вещи.
namespace detail_detectDefinedClass {
template <class Void, class First, class... Rest>
struct detect : detect<Void, Rest...> { };
template <class First, class... Rest>
struct detect<std::void_t<decltype(sizeof(First))>, First, Rest...> {
using type = First;
};
}
template <class... Classes>
using DetectDefinedClass = typename detail_detectDefinedClass::detect<
std::void_t<>, Classes...
>::type;
struct A /* B */ {
};
class C : public DetectDefinedClass<struct A, struct B> {
};
static_assert(std::is_base_of_v<A, C>);
//static_assert(std::is_base_of_v<B, C>);
Это использует SFINAE, пытаясь использовать sizeof
для запрошенного типа, который работает, только если тип был определен (struct A
в списке аргументов шаблона просто объявляет его).
Ответ 5
Вы можете использовать защиту кода класса:
//classA.h
#ifndef cgA
#define cgA
class A {};
#endif
//classB.h
#ifndef cgB
#define cgB
class B {};
#endif
//classC.h
#include classA.h
#include classB.h
#ifdef cgA
class C : public A
#else
class C : public B
#endif
Редактирование с дополнительной информацией о комментариях: Вы можете добавить пустой classA.h и classB.h и ссылаться на них как на последний в вашем includepath. Если ваш фреймворк не содержит A или B, пустые файлы будут загружены.
Ответ 6
Вы можете использовать псевдоним типа и выбрать правильный в соответствии с версией библиотеки
#ifdef LIB_VER_123
typedef A A1;
#else
typedef B A1;
#endif
class C : public A1
{
...
}