A C++ idiom for per-class data, accessible without needing virtual getter methods
(这个问题与反思有关,但实际上不是关于反思)
我有这个类的层次结构(比如
我希望能够为我的每类数据提供非虚拟getter,例如:
1 | /*can it be static? */ const std::string& A::GetFunnyName(); |
最重要的是,我希望继承类中没有或尽可能少的样板代码。getter将在
有人建议(例如在这里的问题中间接地)使用类的类型散列作为键的多功能对象可能是合理解决方案的基础。是这样吗?是否有"标准"代码可以这样做(例如,在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++有静态的虚拟数据成员,这将是微不足道的。但是,使用
我实现了一些具有相同目标的代码,即为每个类提供一个好的名称描述。
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() |
在这里,任何类型的
如果不想使用
查看代码。
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); } |
(活生生的例子)
如果你想让
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",那么应该使用
奇怪的循环模板模式的工作方式如下:
您可以看到模式的基本形式。
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!! "; } ... }; |
派生类继承
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 2 3 4 | struct A { ... std::string GetFunnyName() {return typeid(*this).name();} }; |
为不同派生类返回的字符串将不同;但是,您无法控制这些字符串的外观(它们可能包含类型名称的损坏版本)。
您可能希望使用
这是一个演示。
编辑:因为你真的想和
1 2 3 4 5 | struct A { private: static std::map<std::string, Thing*> my_map; ... } |
然后用它把
1 2 3 4 5 6 | struct A { ... public: Thing& GetFunnyThing() {return *my_map[typeid(*this).name()];} ... }; |
现在,每个派生类都应该使用
1 2 3 4 5 6 | struct A { ... protected: static void RegisterThing(std::string name, Thing* thing) {my_map[name] = thing;} ... } |
只调用一次这个方法,并且在正确的时间,可以以不同的方式实现(就像singleton模式一样),所以我不想通过给出一个例子使事情复杂化。