How do you “not repeat yourself” when giving a class an accessible “name” in C++?

烈酒焚心 提交于 2019-12-07 01:16:47

问题


Consider the following:

class Base {
  public:
    virtual std::string getName() = 0;
    ...
};

class Derived1 : public Base {
  public:
    static std::string getClassName() { return("Derived1"); }
    std::string getName() { return("Derived1"); }
    ...
};

class Derived2 : public Base {
  public:
    static std::string getClassName() { return("Derived2"); }
    std::string getName() { return("Derived2"); }
    ...
};

The idea is that if you have the derived class passed as, say, a template parameter, then you can get its class name via getClassName, while if you have it passed as a pointer to base class, you can get the name via getName.

I have seem a lot of similar questions to this here but all of them seem to ask stuff like "how do I use a static virtual", "why don't static virtuals exist" and various stuff like that, and the answers seem to address that more than what I think the real underlying problem is, which is: how can I avoid having to repeat myself with that code and mentioning the name twice while using as little boilerplate as possible? (Don't Repeat Yourself, or DRY Rule)

I don't want a macro, either.


回答1:


First off, you can re-use getClassName in getName:

class Derived1 : public Base {
  public:
    static std::string getClassName() { return("Derived1"); }
    std::string getName() override { return getClassName(); }
    ...
};

Now, all definitions of getName() are identical, so you can put them in a macro to save on typing (and make them more future-proof):

#define GET_NAME() std::string getName() override { return getClassName(); }

class Derived1 : public Base {
  public:
    static std::string getClassName() { return("Derived1"); }
    GET_NAME()
    ...
};

Or you can bundle getClassName in there as well:

#define GET_NAME(maName) \
  static std::string getClassName() { return(maName); } \
  std::string getName() override { return getClassName(); }

class Derived1 : public Base {
  public:
    GET_NAME("Derived1")
    ...
};

You say "I don't want a macro, either," but macros are a good tool for that, and I wouldn't see a single problem with using them like this. However, if that is not what you want, you can do it without them as well:

template <class Self>
struct GetName : public Base
{
  std::string getName() override { return Self::getClassName(); }
};

class Derived1 : public GetName<Derived1> {
  public:
    static std::string getClassName() { return("Derived1"); }
    ...
};

class Derived2 : public GetName<Derived2> {
  public:
    static std::string getClassName() { return("Derived2"); }
    ...
};



回答2:


Don't fear data:

class Base {
  public:
    std::string const Name;
    Base(std::string Name) : Name(Name) { }

};

class Derived1 : public Base {
  public:
    static const std::string Name;
    Derived1() : Base { Name } { }
};

const std::string Derived1::Name { "Derived1" }



回答3:


Make a separate base class that has the single responsibility of providing a class name string:

class FakeRTTI
{
    std::string class_name;
public:
    FakeRTTI( std::string className ) : class_name(className) {}
    getClassName() { return class_name; }
}

With which you can then do this in all classes that need your fake, inefficient, explicit, string-based RTTI:

class Bla : public FakeRTTI
{
public:
    Bla() : FakeRTTI("Bla") {}
}

Pro's:

  • DRY: there is only one ever use of the string "Bla", in its constructor.
  • Single Responsibility Principle
  • No virtual function calls

Cons:

  • Multiple inheritance (is this is con, really?)
  • You're not using the efficient, standard, C++-based RTTI.
  • You're still using RTTI (it might well not be feasible to get rid of it, but it is a sign of code smell all the smell).



回答4:


Another possible solution uses traits and type erasure as in the following example:

#include<string>
#include<iostream>

template<typename> struct NameTraits;

template<typename T>
struct tag {};

class Base {
    using func = std::string(*)(void);

    template<typename T>
    static std::string name() {
        return NameTraits<T>::name;
    }

public:
    template<typename T>
    Base(tag<T>): nameF{&name<T>} {}

    std::string getName() {
        return nameF();
    }

private:
    func nameF;
};

struct Derived1: Base {
    Derived1(): Base{tag<Derived1>{}} {}
};

struct Derived2: Base {
    Derived2(): Base{tag<Derived2>{}} {}
};

template<> struct NameTraits<Derived1> { static constexpr char *name = "Derived1"; };
template<> struct NameTraits<Derived2> { static constexpr char *name = "Derived2"; };

int main() {
    Base *base = new Derived1;
    // Using base class
    std::cout << base->getName() << std::endl;
    // Using directly the type
    std::cout << NameTraits<Derived2>::name << std::endl;
}

Pros:

  • Name is no longer part of the class and you can easily define a common trait for a family of classes (use simply the same tag for all of them)

  • You don't have any virtual method

  • You don't need two methods that do almost the same thing

Cons:

  • You have to explicitly specify the tag that carries the type to be used during construction


来源:https://stackoverflow.com/questions/39031971/how-do-you-not-repeat-yourself-when-giving-a-class-an-accessible-name-in-c

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!