0

(这个问题与反射有关,但实际上与反射无关)

我有这个类的层次结构(比如class Aclass B : public A),除了特定于实例的数据之外,我还希望所有实例共享特定于类的数据。例如,假设我想为FunnyClassName我的每个类创建一个字符串。

我希望能够为我的每类数据使用非虚拟吸气剂,例如:

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

最重要的是,我希望在继承类中没有或尽可能少的样板代码。class Agetter 将在(类层次结构的根)中实现一次;B 类应该以其他方式指定它的 FunnyClassName。

有人建议(例如,间接地在 SO 上的问题中)使用类的类型哈希作为键的 Multiton 对象可能是合理解决方案的基础。是这样吗?是否有执行此操作的“标准”代码(例如在 STL 或 Boost 中)?还有另一种相关的方法吗?

备注

  • 认为这是不可能的吗?请参阅这个问题这个(简洁的)答案。但正如一些评论者和响应者所建议的那样,可能有必要使用非静态 getter,并进行更弱的约束,即不必为每个类重写 getter(即使用 RTTI)。
  • 如果 C++ 有静态虚拟数据成员,这将是微不足道的 - virtual static const std::string FunnyName。使用静态虚拟方法也是可能的,但前提是我们放弃对仅在基类中实现的 getter 的要求。我们会有类似/* static??*/ static const std::string& A::GetFunnyName() { return "Aye"; }and的东西/* static??*/ const std::string& B::GetFunnyName() { return "Bee"; }
  • 我对模板类的情况并不特别感兴趣,但如果你也想解决这个问题,那很好。
  • FunnyName() 只是一个例子。可能是const Thing& GetFunnyThing()。我不想要 typeid.name() 中的类名称,或者它的去修饰,等等。
  • C++11 没问题,但 C++03 中的解决方案会更好。请不要 C++14。
4

3 回答 3

2

如果不想使用virtual,可以使用模板。这个成语的名字叫Curiously recurring template pattern,用在 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>
{
    ...
};

(活生生的例子)

我的示例代码使用compile-time polymorphism。因此,它不能在运行时应用。如果你想在运行时获得“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
    }
};

On 1,我们将this指针转换为Derived *,并foo用这个转换的指针调用 on 2。由于指针的类型是Derived *thiz->foo();语句将调用Derived::foo

维基百科页面解释似乎不错)

于 2014-08-20T09:15:23.103 回答
2

您的(原始)问题不恰当(或者可以通过这是不可能的方式回答)。一方面你想要getter 将在 A 类(类层次结构的根)中实现一次,就是这样。另一方面,您建议如果 C++ 有静态虚拟数据成员,这将是微不足道的。但是,对于static virtual方法,您仍然需要为每个派生类重新实现 getter,这与您的第一个请求相矛盾。

我已经实现了一些具有相同目的的代码,即为每个类提供一个很好的名称描述。

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.

于 2014-08-20T09:38:43.393 回答
0

我不确定这是否能回答问题,但您应该考虑使用typeid. 这是RTTI的一部分,所以它可以区分静态和动态类型。

在您的基类中有以下代码:

struct A {
    ...
    std::string GetFunnyName() {return typeid(*this).name();}
};

不同派生类返回的字符串会有所不同;但是,您无法控制这些字符串的外观(它们可能包含例如类型名称的错位版本)。

您可能希望使用 astd::map将这些系统生成的名称转换为更喜欢的名称,例如FunnyName1FunnyName2,但您无法提取派生类的名称(或者您可以,但不能以可移植的方式)。

是一个演示。


编辑:由于您真的想使用FunnyThing而不是使用FunnyName,因此您绝对应该使用map. 使其成为静态对象:

struct A {
private:
    static std::map<std::string, Thing*> my_map;
    ...
}

然后使用它转换stringThing

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;}
    ...
}

仅在正确的时间调用此方法一次,可以以不同的方式实现(就像单例模式一样),所以我不想通过举例来使事情复杂化。

于 2014-08-20T11:52:56.040 回答