Проверка существования функции члена С++, возможно, защищенной
Я пытаюсь определить, имеет ли класс определенную функцию (в частности 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
, который включает пользователей, которые объявляют свои классы как окончательные, или используют защищенное наследование где-то на этом пути. Любое решение, которое требует вмешательства с проверяемым фактическим типом, не является допустимым решением.
Ответы
Ответ 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
.