Проверьте, является ли класс полиморфным - PullRequest
7 голосов
/ 10 июля 2009

У нас есть подпроект 'commonUtils', в котором есть много общих фрагментов кода, используемых в родительском проекте. Один такой интересный материал, который я видел, был: -

/*********************************************************************
If T is polymorphic, the compiler is required to evaluate the typeid 
stuff at runtime, and answer will be true.  If T is non-polymorphic, 
the compiler is required to evaluate the typeid stuff at compile time, 
whence answer will remain false
*********************************************************************/
template <class T> 
bool isPolymorphic() { 
   bool answer=false; 
   typeid(answer=true,T()); 
   return answer; 
}

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

class PolyBase {
public:
   virtual ~PolyBase(){}
};

class NPolyBase {
public:
   ~NPolyBase(){}
};


if (isPolymorphic<PolyBase>())
  std::cout<<"PolyBase = Polymorphic\n";
if (isPolymorphic<NPolyBase>())
  std::cout<<"NPolyBase = Also Polymorphic\n";

Но ни один из них никогда не возвращает истину. MSVC 2005 не дает никаких предупреждений, но Comeau предупреждает, что выражение typeid не имеет никакого эффекта. Раздел 5.2.8 в стандарте C ++ не говорит ничего подобного тому, что говорится в комментарии, т.е. typeid оценивается во время компиляции для неполиморфных типов и во время выполнения для полиморфных типов.

1) Так что я думаю, что комментарий вводит в заблуждение / просто неверен, или, поскольку автор этого кода - довольно старший программист C ++, я что-то упустил?

2) ОТО, мне интересно, можем ли мы проверить, является ли класс полиморфным (имеет хотя бы одну виртуальную функцию), используя какую-то технику?

3) Когда можно было бы узнать, является ли класс полиморфным? Грубое предположение; получить начальный адрес класса с помощью dynamic_cast<void*>(T) (поскольку dynamic_cast работает только на полиморфных классах).

В ожидании вашего мнения.

Заранее спасибо,

Ответы [ 4 ]

9 голосов
/ 10 июля 2009

Я не могу представить, как можно использовать этот typeid для проверки того, что тип полиморфен. Его даже нельзя использовать, чтобы утверждать, что это так, так как typeid будет работать с любым типом. Boost имеет реализацию здесь . Что касается того, почему это может быть необходимо - один известный мне случай - библиотека Boost.Serialization. Если вы сохраняете неполиморфный тип, то вы можете просто сохранить его. При сохранении полиморфного вам нужно получить его динамический тип с помощью typeid, а затем вызвать метод сериализации для этого типа (поиск его в некоторой таблице).

Обновление : похоже, я на самом деле не прав. Рассмотрим этот вариант:

template <class T> 
bool isPolymorphic() { 
    bool answer=false;
    T *t = new T();
    typeid(answer=true,*t); 
    delete t;
    return answer; 
}

Это на самом деле работает так, как подсказывает название, именно для комментария в исходном фрагменте кода. Выражение внутри typeid не оценивается, если оно «не обозначает lvalue типа полиморфного класса» (стандарт 3.2 / 2). Таким образом, в случае выше, если T не является полиморфным, выражение typeid не оценивается. Если T полиморфна, то * t действительно является lvalue полиморфного типа, поэтому необходимо вычислить все выражение.

Теперь ваш оригинальный пример все еще не верен :-). Он использовал T(), а не *t. И T() создать значение (стандарт 3.10 / 6). Таким образом, оно все еще дает выражение, которое не является «lvalue полиморфного класса».

Это довольно интересный трюк. С другой стороны, его практическое значение несколько ограничено - потому что, хотя boost :: is_polymorphic дает вам константу времени компиляции, эта дает вам значение времени выполнения, поэтому вы не можете создать экземпляр другого кода для полиморфных и неполиморфных типов. .

3 голосов
/ 10 июля 2009


class PolyBase {
public:   
    virtual ~PolyBase(){}
};

class NPolyBase {
public:
    ~NPolyBase(){}
};

template<class T>
struct IsPolymorphic
{
    struct Derived : T {
        virtual ~Derived();
    };
    enum  { value = sizeof(Derived)==sizeof(T) };
};


void ff()
{
    std::cout << IsPolymorphic<PolyBase >::value << std::endl;
    std::cout << IsPolymorphic<NPolyBase>::value << std::endl;
}

0 голосов
/ 11 декабря 2018

Начиная с C ++ 11, теперь он доступен в заголовке <type_traits> как std::is_polymorphic. Это можно использовать так:

struct PolyBase {
  virtual ~PolyBase() {}
};

struct NPolyBase { 
  ~NPolyBase() {}
};

if (std::is_polymorphic<PolyBase>::value)
  std::cout << "PolyBase = Polymorphic\n";
if (std::is_polymorphic<NPolyBase>::value)
  std::cout << "NPolyBase = Also Polymorphic\n";

Это печатает просто "PolyBase = Polymorphic".

0 голосов
/ 13 июля 2017

Я немного запутался здесь и надеюсь получить комментарии к этому ответу, объясняющие, чего мне не хватает.

Конечно, если вы хотите выяснить, является ли класс полиморфным, все, что вам нужно сделать, это спросить, поддерживает ли он dynamic_cast, не так ли?

template<class T, class> struct is_polymorphic_impl   : false_type {};
template<class T> struct is_polymorphic_impl
    <T, decltype(dynamic_cast<void*>(declval<T*>()))> : true_type {};

template<class T> struct is_polymorphic :
    is_polymorphic_impl<remove_cv_t<T>, void*> {};

Кто-нибудь может указать на недостаток в этой реализации? Я предполагаю, что он должен быть или должен был быть в какой-то момент в прошлом, потому что документация Boost продолжает утверждать, что is_polymorphic "не может быть реализовано переносимым образом на языке C ++".

Но "переносимо" - это своего рода ласка слова, верно? Возможно, они просто намекают на то, что MSVC не поддерживает выражение-SFINAE, или некоторые диалекты, такие как Embedded C ++, не поддерживают dynamic_cast. Возможно, когда они говорят «язык C ++», они имеют в виду «подмножество наименьшего общего знаменателя языка C ++». Но у меня есть ноющее подозрение, что, возможно, они имеют в виду то, что говорят, и Я тот, кто что-то упустил.

Подход typeid в ОП (с поправками, внесенными более поздним ответом, чтобы использовать lvalue, а не rvalue) также выглядит хорошо, но, конечно, это не constexpr, и он требует фактического построения T, что может быть дорого. Так что этот dynamic_cast подход кажется лучше ... если только он по какой-то причине не работает. Мысли

...