Подтвердить что ты не робот

Проверка существования функции члена С++, возможно, защищенной

Я пытаюсь определить, имеет ли класс определенную функцию (в частности shared_from_this(), которая унаследована от std::enable_shared_from_this<Some Unknown Class>). Чтобы усложнить ситуацию, мне нужно знать, имеет ли она эту функцию, даже если она унаследована от удаленного базового класса или унаследована с использованием защищенного доступа.

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

Текущий метод, который я использую, следующий:

template <class T>
struct shared_from_this_wrapper : public T
{
  template <class U>
  static auto check( U const & t ) -> decltype( t.shared_from_this(), std::true_type() );

  static auto check( ... ) -> decltype( std::false_type() );
};

template<class T>
struct has_shared_from_this : decltype(shared_from_this_wrapper<T>::check(std::declval<shared_from_this_wrapper<T>>()))
{ };

Недостатком моего текущего решения является то, что он не работает с объявленными классами final. Поэтому я получаю решение для тестирования функции-члена, которая удовлетворяет:

  • Работа с объявленными классами final
  • Работает с защищенными функциями-членами
  • Работает с наследованием
  • Не нужно знать возвращаемый тип функции
  • Компиляция под gcc, clang и MSVC 2013 (последняя, ​​потенциально ограничивающая чрезмерно причудливую SFINAE)

Изменить: у меня есть потенциальное решение, которое работает, но требует подружиться со вспомогательным классом, который также не является идеальным решением, но, возможно, обходным путем (поскольку он удовлетворяет всем требованиям):

struct access
{
  template <class T>
  static auto shared_from_this( T const & t ) -> decltype( t.shared_from_this() );
};

template <class U>
static auto check( U const & t ) -> decltype( access::shared_from_this(t), std::true_type() );

static auto check( ... ) -> decltype( std::false_type() );

template<class T>
struct has_shared_from_this2 : decltype(check(std::declval<T>()))
{ };

struct A : std::enable_shared_from_this<A> {};
struct B : protected A { friend class access; };    

Другое редактирование: примеры классов и то, что должен проверять наличие признаков типа shared_from_this:

struct A : std::enable_shared_from_this<A> {}; // should return true
struct B final : protected A {}; // should return true
struct C : A {}; // should return true
struct D {}; // should return false

Я должен упомянуть, что моя конечная цель в определении того, существует ли эта функция, заключается в том, чтобы определить возвращаемый тип, чтобы выяснить тип, на котором был template шаблоном std::enable_shared_from_this. Наследование от std::enable_shared_from_this<T> дает вам std::shared_ptr<T> shared_from_this(), а T - это в конечном счете то, что мне нужно выяснить. Это необходимо для правильной сериализации типов, которые наследуются от std::enable_shared_from_this.

Изменить часть 3: Редактирование:

Это делается для библиотеки сериализации cereal, и поэтому у меня есть нулевой контроль над тем, как пользователь хочет создать свой класс, Я хотел бы иметь возможность сериализовать любой тип пользователя, полученный из std::enable_shared_from_this, который включает пользователей, которые объявляют свои классы как окончательные, или используют защищенное наследование где-то на этом пути. Любое решение, которое требует вмешательства с проверяемым фактическим типом, не является допустимым решением.

4b9b3361

Ответ 1

Я поставил некоторые мысли о том, как реализовать то, что вы просили, и пришел к совершенно другому выводу.

Проблема очень интересная: как проверить, реализует ли класс скрытый интерфейс. К сожалению, проблема противоречит принципу подписи Лискова; один из основных объектно-ориентированных принципов.

Отчасти это связано с структурой типа std::shared_ptr. shared_ptr не отражает отношения наследования его типов аргументов. Учитывая класс T и класс U, где class T : public U {}; содержит shared_ptr<T> : public shared_ptr<U> {}; , не!

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

Кроме того, если std::shared_ptr становится устаревшим или вы хотите использовать другие средства для хранения памяти (std::allocator interface? some region/pool allocation), вам придется адаптировать свои интерфейсы.

Мое личное мнение - создать какой-то интерфейс factory и зарегистрировать его где-нибудь в десериализаторе.

Второй должен состоять в том, чтобы иметь класс factory, который предоставляет неявный интерфейс шаблона (и используйте CRTP, чтобы специализировать интерфейс для пользователям, то есть:

template <class ActualType, 
          class IfType=ActualType,
          class Allocator=default::allocator<ActualType>>
class Deserializable {
  static IfType alloc(ActualType &&t) {
    Allocator a; // choose  another interface as your please.
    return a.allocate(t); /* implement me */
  }
private:
};

class MyClass
 : public InterfaceClass,
   public Deserializable<MyClass,InterfaceClass> {
  /* your stuff here */
};
  • Это дает вам достаточную абстракцию в ваших классах шаблонов.
  • Пользователь вашей библиотеки в любом случае знает, чего хочет. и если он захочет выделить что-то еще, чем std::shared_ptr, он мог бы сделать (создав свой собственный Allocator)
  • Пользователь не должен ничего реализовывать, кроме указания типов (и фактически передает их вам, поэтому никаких вторых догадок).

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

Чтобы понять силу этого подхода, я приветствую вас, чтобы посмотреть код boost::operator, который использует этот метод, чтобы указать возвращаемый тип и аргументы арифметических операторов во время компиляции.

Примечание

Для людей, которые также рассматривают это сообщение для ответов на исходную проблему, я предлагаю использовать этот подход. Однако для этого требуется, чтобы член был общедоступным, поскольку он проверяет указатель на функцию-член имени.

Ответ 2

Я собираюсь позволить себе расспросить вопрос. Не каждый объект, прошедший через shared_ptr, наследуется от enable_shared_from_this.

Возможно, это будет то, что вы ищете или даете некоторые дополнительные идеи:

class Foo1 { };
class Foo2 : public std::enable_shared_from_this< Foo2 > {};
class Foo3  final : protected Foo2 {};

struct Serialize
{
    template <typename T>
    void write( T* ) {  printf( "not shared!\n" ); }

    template <typename T>
    void write( std::shared_ptr<T> ) { printf( "shared!\n" ); }
};

int test( )
{
    typedef std::shared_ptr<Foo2> Foo2Ptr;
    typedef std::shared_ptr<Foo3> Foo3Ptr;

    Serialize   s;
    Foo1*       pFoo1 = nullptr;
    Foo2Ptr     pFoo2;
    Foo3Ptr     pFoo3;
    s.write( pFoo1 );
    s.write( pFoo2 );
    s.write( pFoo3 );

    return 0;
}

Во время выполнения выход:

not shared!
shared!
shared!

Ответ 3

Если единственная цель - определить тип T, то я предлагаю вам сделать это в STL и добавить typedef:

template <class T>
struct enable_shared_from_this : public T
{
    typedef T base_type;
    // ...
};

Затем вы можете использовать его так:

class A : enable_shared_from_this<B>
{
}

A::base_type // == B

В этом примере предполагается, что вы знаете, что A наследуется от shared_from_this_wrapper.