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