关于设计模式:每类数据的C ++习语,无需虚拟getter方法即可访问

A C++ idiom for per-class data, accessible without needing virtual getter methods

(这个问题与反思有关,但实际上不是关于反思)

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

我希望能够为我的每类数据提供非虚拟getter,例如:

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

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

有人建议(例如在这里的问题中间接地)使用类的类型散列作为键的多功能对象可能是合理解决方案的基础。是这样吗?是否有"标准"代码可以这样做(例如,在STL或Boost中)?还有其他相关的方法吗?

笔记:

  • 认为这是不可能的?看到这个问题和这个(简洁的)答案。但是正如一些评论者和响应者所说,可能有必要使用非静态getter,并使不必为每个类(即使用rtti)重写getter的约束变弱。
  • 如果C++有静态的虚拟数据成员,那么这将是微不足道的——EDCOX1 OR 4。对于静态虚拟方法,这也是可能的,但前提是我们不再需要只在基类中实现的getter。我们会有类似于/* 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。


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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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的专业化。如果两种情况都不存在,那么名称是从demangling the typeid中提取出来的。


如果不想使用virtual,可以使用模板。这个成语的名称是一个奇怪的重复出现的模板模式,在ATL和WTL中使用。

查看代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#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
{
public:
    std::string GetFunnyName() const
    {
        return"B";
    }
};

template <typename TSuper>
void OutputFunny(const TSuper &obj)
{
    std::cout << obj.GetFunnyName() <<"
"
;
}

int main()
{
    A a;
    B b;

    OutputFunny(a);
    OutputFunny(b);
}

(活生生的例子)

如果你想让B继承A的话,代码如下:

1
2
3
4
5
6
7
8
9
10
11
template <typename C>
class A_base : public Super<C>
{
    ...
};
class A : public A_base<A> { };

class B : public A_base
{
    ...
};

(活生生的例子)

我的示例代码使用编译时多态性。因此,它不能在运行时应用。如果您想在运行时获得"funnyname",那么应该使用virtual运行时多态性。


奇怪的循环模板模式的工作方式如下:

您可以看到模式的基本形式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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!!
"
;
    }
    ...
};

派生类继承SuperDerived本身作为模板参数。

Super的具体化如下:

1
2
3
4
5
6
7
8
9
template <>
class Super<Derived>
{
    void foo()
    {
        Derived *thiz = static_cast<Derived *>(this); // 1
        thiz->foo();                                  // 2
    }
};

1上,我们把this指针投射到Derived *上,用这个投射指针在2上调用foo。由于指针类型为Derived *thiz->foo();语句将调用Derived::foo

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


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

在基类中包含以下代码:

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

为不同派生类返回的字符串将不同;但是,您无法控制这些字符串的外观(它们可能包含类型名称的损坏版本)。

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

这是一个演示。

编辑:因为你真的想和FunnyThing合作,而不是和FunnyName合作,所以你一定要使用map。使其成为静态对象:

1
2
3
4
5
struct A {
private:
    static std::map<std::string, Thing*> my_map;
    ...
}

然后用它把string转换成Thing

1
2
3
4
5
6
struct A {
    ...
public:
    Thing& GetFunnyThing() {return *my_map[typeid(*this).name()];}
    ...
};

现在,每个派生类都应该使用RegisterThing来"声明"它想要返回的Thing

1
2
3
4
5
6
struct A {
    ...
protected:
    static void RegisterThing(std::string name, Thing* thing) {my_map[name] = thing;}
    ...
}

只调用一次这个方法,并且在正确的时间,可以以不同的方式实现(就像singleton模式一样),所以我不想通过给出一个例子使事情复杂化。