Идиома 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(), или его demangling, или что-то подобное.
  • C++ 11 в порядке, но решение в C++03 было бы лучше. Нет C++14, пожалуйста.

3 ответа

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

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

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

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

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