据我从模糊的“宏继续...”评论中可以看出,DECLARATION 宏似乎做了以下一件或几件事情:
- 定义成员独立于
ABC
- 定义依赖于类型的成员(方法或数据成员)
ABC
- 定义使用字符串的成员
"ABC"
tl;dr:正如您在问题中指出的那样,#1 是微不足道的。 #2 比较简单,使用 CRTP,#3 没有宏就不能令人满意。
更新:
在下面的 cmets 中,您提到您希望保证字符串和类名是“同步的”。如果没有当前 C++ 中的宏,这绝对是不可能的。 (参见下面的“更新”)
第一个很容易在没有宏的情况下实现 - 只需从定义成员的普通基类继承即可。
第二个也可以相对简单地完成,通过 CRTP:创建一个模板类,将类型作为参数并从模板继承,并使用类型本身进行实例化:
template <class T>
class Base {
public:
void doSomething(T const&);
};
class ABC : public Base<ABC> {
// inherited void doSomething(ABC const&);
};
第三个很棘手,如果没有至少一点样板文件就不容易解决。
除了将名称(类名、函数名、变量名...)转换为代表该名称的字符串的宏之外,C++ 没有其他功能。 (这种语言特征是通常称为反射的一系列特征的一部分)。 typeid(T).name() 是一个例外,但结果不是标准化的,所以它可能会或可能不会给你类的名称,不同的东西,可读或不可读或只是一个空字符串。
因此,要(可移植且可靠地)为类ABC 获取字符串“ABC”,除了类定义之外,您必须至少编写一次“ABC”。这还不算太糟糕,您也必须为#2 做类似的事情,因为它是seems impossible to use the type of ABC without explicitly mentioning ABC again。
更新:由于您必须显式键入字符串和类名,因此无法保证两者始终相同,除非使用宏。即使您在问题中拥有的宏也容易发生更改/错别字:
class ABC {
DECLARATION(ACB);
};
如果宏只提供它的参数的字符串化版本而不对类型做任何事情,这将给出一个错误的类名(在这种情况下,编译器会告诉你 ACB 不是类型)。如果您想要保证,请使用一个可以做所有事情的宏,包括类定义:
#define CLASS_DEF(x) \
class x##_base { \
std::string GetClassName() { \
return std::string(#x); \
} \
}; \
class x : public x##_base
然后:
CLASS_DEF(ABC)
{
private:
void ABCFun1();
void ABCFun2();
// ... and so on
}
将它放在基类中可以编写宏,然后只是一个普通的类体。
如果我们不需要同步保证,那么如果我们不需要宏,我们可以为#3 做些什么呢?
-
将字符串作为模板参数提供给某个基类将是最好的方法,但简而言之,这是不可能的。 This article 将字符串作为模板参数,最后不得不退回到宏。我们能得到的最接近的是文章中提到的某种形式的boost::mpl::string<'Hell','o Wo','rld!'>。如果 C++11 可变参数模板不可用,长度将受到一些任意定义的限制,并且基类(或boost::mpl::string)的定义本身可能会使用大量的宏魔法,但至少你是摆脱类本身中的自定义宏。使用 #2 和 #3 的东西必须看起来有点像(我将名称加长以说明该方法的限制)
class ABCDE: public Base<ABCDE, 'ABCD', 'E'> { /* ... */ }
-
将字符串作为具有预定义名称的静态数据成员常量。这是一个非常适合与 CRTP 一起使用的选项,如果您无论如何都必须使用它:
template <class T>
class Base {
public:
std::string GetClassName() const {
return T::name;
};
};
class ABC : public Base<ABC> {
public:
constexpr static char const * name = "ABC";
};
在 C++03 中,您必须使用必须在类定义之外定义的 static const 成员(即在 ABC.cpp 中)
-
将字符串作为 ABC 的非静态数据成员的工作方式类似:
template <class T>
class Base {
public:
std::string GetClassName() const {
return static_cast<T const*>(this)->name;
};
};
class ABC : public Base<ABC> {
public:
const std::string name = "ABC";
};
这需要 C++11 进行类内成员初始化。在 C++03 中,名称必须在每个构造函数中正确初始化。
-
将字符串作为基类的数据成员有效,并且应该是 imo 的首选,如果您没有类型相关的成员,即如果您不需要 CRTP 来执行 #2 :
class Base {
public:
Base(std::string nm) : name(nm) {}
std::string GetClassName() const {
return name;
};
std::string name;
};
class ABC : public Base {
public:
ABC() : Base("ABC") {}
ABC(int i) : ABC() { /*another ctor*/ }
};
只需使用 C++11 委托构造函数就可以使用类名初始化 Base,尽管在每个初始化列表中写入 Base("ABC") 或 ABC() 并没有太大区别。