用静态方法进行C++设计

C++ design with static methods

我想用静态方法定义为X类:

1
2
3
4
5
class X
{
 static string get_type () {return"X";}
 //other virtual methods
}

我想强制从x继承的类重新定义get_type()方法并返回不同于"x"的字符串(如果他们现在重新定义get_类型,我很高兴)。

我该怎么做?我知道我不能有虚拟静态方法。

编辑:问题不是关于类型_ID,而是关于一个静态方法,应该被覆盖。例如

1
2
3
class X {
 static int getid() {return 1;}
}


1
2
3
4
5
6
7
8
template<int id>
class X {
public:
    static int getid() { return id; }
};

class Y : public X<2> {
};

您没有重写该方法,但是您已经强制每个子类提供一个ID。警告:我没有尝试过这个方法,可能有一些微妙的原因导致它不能工作。


如果我没有弄错,要调用静态方法,您必须通过指定类的确切名称来调用该方法,例如X::get_type();DerivedClass::get_type()等;在任何情况下,如果调用对象,则不考虑对象的动态类型。因此,至少在特定的情况下,它可能只在模板化的上下文中有用,而不希望出现多态行为。

但是,我不明白为什么不应该强制每个有趣的类(继承或不继承,因为"编译时多态性"并不关心)为这个功能提供模板。在以下情况下,您必须专门化get_type函数,否则将出现编译时错误:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <string>

struct X {};
struct Derived: X {};

template <class T> std::string get_type() {
    static_assert(sizeof(T) == 0,"get_type not specialized for given type");
    return std::string();
}

template <> std::string get_type<X>() {
    return"X";
}

int main() {
    get_type<X>();
    get_type<Derived>(); //error
}

(EDOCX1)4是C++0x,否则使用您最喜欢的实现,例如EDCOX1(5)。如果您对专门化函数感到不好,请改为专门化一个结构。如果你想强制一个错误,如果有人不小心试图将它专门化为不是从x派生的类型,那么这也可以用type_traits实现。)


我想说你知道原因,但为了以防万一,这里有一个很好的解释:

http://publib.boulder.ibm.com/infocenter/lnxpcomp/v8v101/index.jsp?topic=/com.ibm.xlcpp8l.doc/language/ref/cpl139.htm(主题=/com.ibm.xlcpp8l.doc/language/ref/cpl139.htm)

看来你得设计出一种解决办法。也许是一个包装单例的虚拟函数?


很抱歉恢复了这条线,但我也刚刚遇到了这个道德危机。这是一个非常大胆和可能愚蠢的说法,但我完全不同意大多数人所说的关于static virtual没有任何意义。这种困境源于静态成员是如何被普遍使用的,而不是他们实际上在下面做什么。

人们通常使用静态类和/或成员来表达事实-如果实例相关,那么对于所有实例都是这样,或者对于静态类来说,仅仅是关于世界的事实。假设你在模拟一门哲学课。你可以把abstract class Theory定义为代表一个要教授的理论,然后从TheoryOfSelfTheoryOfMind等中的Theory继承。为了教授一个理论,你真的需要一种叫做express()的方法,它使用一种适合听众的特定词组来表达一个理论。可以假定任何继承类都应该公开相同的方法express()。如果我能够,我将使用static virtual Theory.express()来建模这种关系——它既是超越实例概念(因此是静态的)的事实陈述,也是非特定的,需要每种理论(因此是虚拟的)的具体实现。

但是,我完全同意那些以static实际所做的为理由来证明禁止的人的观点——从编码原则上讲,这是完全合理的,这个问题源自人们通常模拟现实世界的习惯方式。

我能想到的解决这个问题的最好办法是把Theory模型化为一个单例——可能有一个理论的实例,但只有一个。如果您想要一个可选的,它是一个不同的类型,所以创建一个新的派生类。对我来说,这种方法似乎是武断的,引入了不必要的噪音。


使用delphi,它支持类上的虚拟静态成员。>


您提到了一些关于确保子类型为您的函数生成唯一值的地方。正如其他人所说,这在编译时是不可能的[至少,没有使用模板,这可能是或可能是不可接受的]。但是如果你把它延迟到运行时,你可能会得到类似的东西。

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
class Base {
  static std::vector<std::pair<const std::type_info*, int> > datas;
  typedef std::vector<std::pair<const std::type_info*, int> >::iterator iterator;
public:
  virtual ~Base() { }
  int Data() const {
    const std::type_info& info = typeid(*this);
    for(iterator i = datas.begin(); i != datas.end(); ++i)
      if(*(i->first) == info) return i->second;
    throw"Unregistered Type";
  }

  static bool RegisterClass(const Base& p, int data) {
    const std::type_info& info = typeid(p);
    for(iterator i = datas.begin(); i != datas.end(); ++i) {
      if(i->second == data) {
    if(*(i->first) != info) throw"Duplicate Data";
    return true;
      }
      if(*(i->first) == info) throw"Reregistering";
    }
    datas.push_back(std::make_pair(&info, data));
    return true;
  }
};
std::vector<std::pair<const std::type_info*, int> > Base::datas;

class Derived : public Base { };
const DerivedRegisterFlag = Base::RegisterClass(Derived(), 10);

class OtherDerived : public Base { };
const OtherDerivedRegisterFlag = Base::RegisterClass(OtherDerived(), 10); //exception

注意:这是完全未经测试的。如果这样做,在进入main之前会抛出异常。如果愿意的话,可以将注册转移到构造函数中,并接受注册检查的每个实例开销。

为了简单起见,我选择了一个无序向量;我不确定type_info::before是否提供了必要的语义来用作映射的谓词,而且假设您没有那么多派生类,线性搜索无论如何都会有问题。我存储指针是因为您不能直接复制type_info对象。这是最安全的,因为typeid返回的对象的生存期是整个程序。程序关闭时可能有问题,我不确定。

我没有试图防止初始化错误的静态顺序。如文所述,这将在某个时候失败。

最后,不,它不是静态的,但是"静态的"和"虚拟的"无论如何都没有真正意义。如果您没有要操作的类型实例,那么如何知道要选择哪个覆盖方法?对于模板,有一些情况下您可能会合法地希望调用没有实际对象的静态方法,但这不太常见。

*编辑:另外,我不确定它如何与动态链接库等交互。我怀疑RTTI在这些情况下是不可靠的,所以很明显这也是不可靠的。


干得好

1
2
3
4
5
6
7
8
9
class X
{
  static string get_type() {return"X"; }
};

class Y : public X
{
  static string get_type() {return"Y"; }
};

上面的代码完全按照您的要求执行:派生类重新定义get_type,并返回一个不同的字符串。如果这不是你想要的,你必须解释原因。您必须解释您试图做什么,以及从静态方法期望的行为。如果你最初的问题完全不清楚。


你不能这样做有很多原因。您不能在x中定义函数,并让它是纯虚拟的。您根本不能拥有虚拟静态函数。

为什么它们必须是静态的?


长话短说,你做不到。要求派生类重写基类函数的唯一方法是使其成为纯虚拟(不能是静态的)。


不要这样做,用typeid代替。