(Этот вопрос относится к рефлексии, но не относится к рефлексии)
У меня есть эта иерархия классов (скажем, class A
а также class B : public A
), и в дополнение к данным, относящимся к конкретному экземпляру, я хотел бы, чтобы данные, относящиеся к классу, использовались всеми экземплярами. Например, предположим, что я хочу иметь FunnyClassName
Строка для каждого из моих классов.
Я хотел бы иметь возможность невиртуальном геттеры для моих данных на класс, такие как:
/*can it be static? */ const std::string& A::GetFunnyName();
и самое главное, я хочу, чтобы в наследуемых классах не было или как можно меньше шаблонного кода. Получатели должны быть реализованы один раз в class A
(корень иерархии классов); класс B должен указать свое FunnyClassName другим способом.
Было высказано предположение (например, косвенно в вопросах здесь о SO), что объект Multiton, использующий хэш типа класса в качестве ключа, может быть основой разумного решения. Это тот случай? Есть ли «стандартный» код, который делает это (например, в STL или в Boost)? Есть ли другой подход?
Заметки:
virtual static const std::string FunnyName
, Со статическими виртуальными методами это также было бы возможно, но только если мы отбросим нашу потребность в геттере, реализуемом только в базовом классе. У нас было бы что-то вроде /* static??*/ static const std::string& A::GetFunnyName() { return "Aye"; }
а также /* static??*/ const std::string& B::GetFunnyName() { return "Bee"; }
,const Thing& GetFunnyThing()
, Я не хочу ничего похожего на имя класса из typeid.name (), его разделение или что-то подобное.Если вы не хотите использовать virtual
Вы можете использовать шаблоны. Название этой идиомы Любопытно повторяющийся шаблон, и он используется в ATL и WTL.
Смотри код.
#include <iostream>
#include <string>
template <typename C>
class Super
{
public:
std::string GetFunnyName() const
{
C *thiz = static_cast<C *>(this);
return thiz->GetFunnyName();
}
};
class A : public Super<A>
{
public:
std::string GetFunnyName() const
{
return "A";
}
};
class B : public Super<B>
{
public:
std::string GetFunnyName() const
{
return "B";
}
};
template <typename TSuper>
void OutputFunny(const TSuper &obj)
{
std::cout << obj.GetFunnyName() << "\n";
}
int main()
{
A a;
B b;
OutputFunny(a);
OutputFunny(b);
}
Если вы хотите сделать B
унаследовать A
код, как это:
template <typename C>
class A_base : public Super<C>
{
...
};
class A : public A_base<A> { };
class B : public A_base<B>
{
...
};
Мой пример кода использует полиморфизм во время компиляции. Таким образом, он не может быть применен во время выполнения. Если вы хотите получить «FunnyName» во время выполнения, вы должны использовать virtual
, полиморфизм во время выполнения.
Любопытно, что повторяющийся шаблон работает так:
Вы можете увидеть основную форму шаблона.
template <typename C>
class Super
{
void foo()
{
C *thiz = static_cast<C *>(this);
thiz->foo();
}
...
};
class Derived : public Super<Derived>
{
void foo()
{
std::cout << "fooo!!\n";
}
...
};
Производный класс наследует Super
, с Derived
сам как параметр шаблона.
Super<Derived>
конкретизируется так:
template <>
class Super<Derived>
{
void foo()
{
Derived *thiz = static_cast<Derived *>(this); // 1
thiz->foo(); // 2
}
};
На 1
мы кастуем this
указатель на Derived *
и позвоните foo
с этим приведенным указателем 2
, Поскольку тип указателя Derived *
, thiz->foo();
заявление позвонит Derived::foo
,
(страница википедии объяснение кажется хорошим)
Ваш (оригинальный) вопрос некорректен (или на него можно ответить это невозможно). Одна рука, которую вы хотите Получатели должны быть реализованы один раз в классе A (корень иерархии классов), и это. С другой стороны, вы предлагаете Если бы C ++ имел статические виртуальные члены-данные, это было бы тривиально. Однако с static virtual
Методы, которые вам все равно понадобятся для повторной реализации методов получения для каждого производного класса, что противоречит вашему первому запросу.
Я реализовал некоторый код с той же целью, то есть дал хорошее описание имени для каждого класса.
namespace some_namespace {
/// demangles symbol name as returned by typeid(T).name()
std::string demangle(const char*mangled_name);
inline std::string demangle(std::string const&mangled_name)
{ return demangle(mangled_name.c_str()); }
/// provides the name for any type
template<typename T>
struct name_traits
{
private:
template<typename U, U> struct check;
template<typename U>
static std::true_type test(check<std::string(*)(), &U::name_of_type>*);
template<typename U>
static std::false_type test(...);
// NOTE what_type required to trick icpc 14.0.2 to compile
typedef decltype(test<T>(0)) what_type;
/// true if static std::string T::name_of_type() exists
static constexpr bool has_name_of_type = what_type::value;
/// return name of types with static std::string name_of_type()
template<bool S>
static enable_if_t< S, std::string>
name_t() { return T::name_of_type(); }
/// return name of all other types: demangle typeid(T).name();
template<bool S>
static enable_if_t<!S, std::string>
name_t()
{ return demangle(typeid(T).name()); }
public:
static std::string name()
{ return name_t<has_name_of_type>(); }
};
}
/// macro returning the name of a given type.
#define nameof(TYPE) some_namespace::name_traits<TYPE>::name()
Здесь любой тип A
может быть оснащен std::string A::name_of_type();
предоставить информацию или специализацию struct some_namespace::name_traits<A>
может дано. Если ни то, ни другое не происходит, название взято из разборки typeid
,
Я не уверен, что это отвечает на вопрос, но вы должны рассмотреть возможность использования typeid
. Это часть RTTI, поэтому он может различать статические и динамические типы.
Имейте следующий код в вашем базовом классе:
struct A {
...
std::string GetFunnyName() {return typeid(*this).name();}
};
Строки, возвращаемые для разных производных классов, будут разными; однако вы не можете контролировать, как будут выглядеть эти строки (они могут содержать, например, искаженную версию имени типа).
Вы можете использовать std::map
перевести эти сгенерированные системой имена в более предпочтительные, такие как FunnyName1
, FunnyName2
и т. д., но вы не можете извлечь имя производного класса (или, может быть, вы можете, но не переносимым способом).
Вот это демо
Изменить: так как вы действительно хотите работать с FunnyThing
а не с FunnyName
Вы должны определенно использовать map
, Сделайте это статическим объектом:
struct A {
private:
static std::map<std::string, Thing*> my_map;
...
}
Тогда используйте это, чтобы преобразовать string
в Thing
:
struct A {
...
public:
Thing& GetFunnyThing() {return *my_map[typeid(*this).name()];}
...
};
Теперь каждый производный класс должен использовать RegisterThing
«объявить», который Thing
он хочет вернуться.
struct A {
...
protected:
static void RegisterThing(std::string name, Thing* thing) {my_map[name] = thing;}
...
}
Вызов этого метода только один раз и в нужное время может быть реализован по-разному (как может быть шаблон Singleton), поэтому я не хочу усложнять ситуацию, приводя пример.