шаблоны проектирования — идиома C ++ для данных каждого класса, доступная без использования виртуальных методов получения

(Этот вопрос относится к рефлексии, но не относится к рефлексии)

У меня есть эта иерархия классов (скажем, class A а также class B : public A), и в дополнение к данным, относящимся к конкретному экземпляру, я хотел бы, чтобы данные, относящиеся к классу, использовались всеми экземплярами. Например, предположим, что я хочу иметь FunnyClassName Строка для каждого из моих классов.

Я хотел бы иметь возможность невиртуальном геттеры для моих данных на класс, такие как:

/*can it be static? */ const std::string& A::GetFunnyName();

и самое главное, я хочу, чтобы в наследуемых классах не было или как можно меньше шаблонного кода. Получатели должны быть реализованы один раз в class A (корень иерархии классов); класс B должен указать свое FunnyClassName другим способом.

Было высказано предположение (например, косвенно в вопросах здесь о SO), что объект Multiton, использующий хэш типа класса в качестве ключа, может быть основой разумного решения. Это тот случай? Есть ли «стандартный» код, который делает это (например, в STL или в Boost)? Есть ли другой подход?

Заметки:

  • Думаешь, это невозможно? Увидеть этот вопрос а также этот (лаконичный) ответ. Но, как предполагают некоторые комментаторы и респонденты, может быть необходимо иметь нестатические геттеры и устанавливать более слабое ограничение на отсутствие необходимости переписывать геттер для каждого класса (т. Е. Используя RTTI).
  • Если бы C ++ имел статические виртуальные члены-данные, это было бы тривиально — virtual static const std::string FunnyName, Со статическими виртуальными методами это также было бы возможно, но только если мы отбросим нашу потребность в геттере, реализуемом только в базовом классе. У нас было бы что-то вроде /* static??*/ static const std::string& A::GetFunnyName() { return "Aye"; } а также /* static??*/ const std::string& B::GetFunnyName() { return "Bee"; },
  • Меня не особенно интересует случай шаблонных классов, но если вы хотите заняться этим также, это хорошо.
  • FunnyName () является лишь примером. Возможно const Thing& GetFunnyThing(), Я не хочу ничего похожего на имя класса из typeid.name (), его разделение или что-то подобное.
  • C ++ 11 в порядке, но решение в C ++ 03 было бы лучше. Нет C ++ 14, пожалуйста.

1

Решение

Если вы не хотите использовать 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,

(страница википедии объяснение кажется хорошим)

2

Другие решения

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

2

Я не уверен, что это отвечает на вопрос, но вы должны рассмотреть возможность использования 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), поэтому я не хочу усложнять ситуацию, приводя пример.

0
По вопросам рекламы [email protected]