如何用继承或模板(或其他任何东西?)替换这个宏?

时间:2014-02-27 09:02:46

标签: c++ templates inheritance macros

我的问题类似于:Can't use macro define class in C++,但有点复杂:

class ABC
{
public:
    DECLARATION(ABC)

private:
    void ABCFun1();
    void ABCFun2();
    // ... and so on
}

#define DECLARATION(TYPE)\
    std::string GetClassName()\
    {\
        return std::string(#TYPE);
    }\
    // the macro can goes on to declare more
    // common interfaces, like Initialize(), ...etc.

所以,重点是,我可以使用这个宏来生成其他类,如DEFGHI ...等,
所有人都共享DECLARATION的共同部分,但也拥有自己的private 部分。

如果不需要GetClassName(),似乎只能使用宏#来实现, 那么我可以用继承把它们放在一起 或者,如果没有私人物品,我可以使用模板。

所以,随着这两件事情的混淆,有没有办法避免宏? 谢谢!

3 个答案:

答案 0 :(得分:2)

您可以CTRP生成一些函数。但是请记住,除非你使用宏,否则名称将不可用(当编译时反射将作为标准时,将会改变,可能是C ++ 17)。

<强> CRTP

template<typename T>
class Base
{
public:
    void myFunc()
    {
        ((T*)this)->functionA();
        ((T*)this)->functionB();
        // ...
    }
};

class ABC : public Base<ABC>
{
    // ...
};

编辑:BЈовић回答是对的,但有关RTTI的一些评论:

  • 不便携
  • typeid(T).name()依赖于实现(如果实现者想要,它可以为每种类型返回""
  • 很慢。

例如,请参阅this question about RTTI and LLVM(或this one about performance)。

要存储字符串,您应该立即使用预处理器。请注意,它不适用于模板化参数(即,如果您有template<typename T>,则#T将扩展为"T",而不是实际的类型名称。 然后,有一种技术允许将字符串传递给模板参数(sort-of):

#define NAME( _n_ , _t_ )             \
    struct _t_                        \
    {                                 \
        static const char* asName()   \
        {                             \
            return #_n_;              \
        }                             \
    }

它将字符串更改为类型。字符串本身就是一个字符串,因此它被直接放入可执行文件中(并且是只读的,尝试修改它很可能会导致崩溃)。

我在SPARK粒子引擎中使用它,例如,我实现了一个反射模块。

答案 1 :(得分:1)

据我所知,“宏观继续......”评论,DECLARATION宏似乎做了其中一件或几件事:

  1. 定义成员独立于ABC
  2. 定义依赖于类型的成员(方法或数据成员) ABC
  3. 定义使用字符串 "ABC"
  4. 的成员

    tl; dr :正如您在问题中所述,#1是微不足道的。 #2相对容易,使用CRTP,没有宏,#3不能令人满意。

    更新

    在下面的评论中,您提到要保证字符串和类名称为“sync-ed”。如果没有当前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()并没有太大区别。

答案 2 :(得分:0)

如果您只需要获取类型的名称,则可以使用typeinfo::name()方法。

您可以使用CRTP,其中包含您需要的所有内容。像这样:

template< typename T >
class MyBase
{
  public:
    MyBase() : className( typeid(T).name() ){}
    virtual ~MyBase(){}


    std::string className;
    // common interfaces, like Initialize()
};

然后使用它:

class A : public MyBase<A>
{
};