Как проверить, объявлен ли класс в C++? - программирование
Подтвердить что ты не робот

Как проверить, объявлен ли класс в 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
{
...
};

Примечание: мне пришло в голову попробовать проверить версию фреймворка, но ответ на этот вопрос меня интересует. Я также не могу изменить какие-либо рамки заголовочных файлов.

РЕДАКТИРОВАТЬ: принятый ответ зависит от того, определен ли класс (что подразумевает объявление), и, в моем случае, класс объявляется, если он определен.

4b9b3361

Ответ 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
{ 
   ...
}