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

Я пытаюсь определить, имеет ли класс определенную функцию (особенно 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, Итак, я после решения для тестирования функции-члена, которая удовлетворяет:

  1. Работает с объявленными классами final
  2. Работает с защищенными функциями-членами
  3. Работает с наследством
  4. Не нужно знать тип возвращаемого значения функции
  5. Компилируется под 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

Я должен упомянуть, что моя конечная цель при определении того, существует ли эта функция, состоит в том, чтобы определить тип ее возврата, чтобы выяснить тип, на котором 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: Редактирование:

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

3 ответа

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

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

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

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

Кроме того, если std::shared_ptr устарела или вы хотите использовать другие средства для получения памяти (std::allocator интерфейс? Вы должны адаптировать свои интерфейсы.

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

Второй - иметь фабричный класс, который предоставляет неявный шаблонный интерфейс (и использовать 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 который использует эту технику для указания типа возвращаемого значения и аргументов арифметических операторов во время компиляции.

Заметка

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

Я позволю себе задать вопрос. Не каждый объект, переданный через 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!

Если единственной целью является определение типа 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,

Другие вопросы по тегам