7

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.

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
The_Sympathizer
  • 1,191
  • 9
  • 17
  • Why not use [RTTI](https://msdn.microsoft.com/en-us/library/b2ay8610.aspx)? – nvoigt Aug 19 '16 at 05:55
  • 1
    In C++11, one can have a `constexpr` string literal member which captures the class name. You can then return it via member function which again returns `constexpr` string literal. Not sure if you are using C++11 though. – Recker Aug 19 '16 at 05:58
  • @nvoigt: This: https://www.reddit.com/r/learnprogramming/comments/1jzlon/cwhy_exactly_is_rtti_and_typeid_bad/ suggests RTTI is bad. – The_Sympathizer Aug 19 '16 at 05:58
  • 3
    Not "bad" so much as "incurring cost". Question: Why would you *need* the name of a class as a string in the first place? That's a requirement I have not encountered the last decade and a half... – DevSolar Aug 19 '16 at 06:00
  • @Recker: But then don't you need to boilerplate-repeat the member function for each derived class? – The_Sympathizer Aug 19 '16 at 06:00
  • What you are asking is some standalone God mode function that would return the name of the class as string literal to effectively avoid the boilerplace...Not sure if its related to [this](http://stackoverflow.com/questions/41453/how-can-i-add-reflection-to-a-c-application) .But you can do that something like `boost::fusion::map` with class type being a key and value being its string literal name. And then fetch the name as and when needed. – Recker Aug 19 '16 at 06:03
  • This can be implemented via preprocessor macro. – Marian Spanik Aug 19 '16 at 06:03
  • @DevSolar: The name is just to identify the type of class, because there's a part of the program that stores a library of instances of these various derived classes in one array as pointer-to-base. I want to be able to request a specific class from that array, so I need the name to figure out which pointer can be safely downcast to the derived type. I don't want to use dynamic_cast because of its slowness. – The_Sympathizer Aug 19 '16 at 06:03
  • @Marian Spanik: yeah, a macro works but I was wondering if there was a "preprocessor-free" solution. – The_Sympathizer Aug 19 '16 at 06:04
  • You won't get faster by comparing strings, most likely... – DevSolar Aug 19 '16 at 06:05
  • @DevSolar: But I run into the same problem were I to associate an int or enum, or something like that ... – The_Sympathizer Aug 19 '16 at 06:09
  • If you don't want to write the string down twice, but you don't have to use the class name (so you can write the string *once*), the solution is easy: use a static constant string which holds the class name. The respective functions only return the string. This way, you only add two lines (the declaration and the definition) and don't repeat yourself. – pschulz Aug 19 '16 at 06:11
  • 3
    RTTI is *bad* because it's a hint you are not doing good OOP. Doing your own homebrew RTTI does not make it better OOP, it just means you are reinventing the wheel on top of bad OOP. – nvoigt Aug 19 '16 at 06:18
  • @nvoigt: So what is the _correct_ way to do this kind of thing (see my post to DevSolar)? – The_Sympathizer Aug 19 '16 at 09:01
  • The reddit you linked has a very nice solution to your problem. What's wrong with that? – nvoigt Aug 19 '16 at 09:25

4 Answers4

4

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"); }
    ...
};
Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • Just to extend the last example: CRTP in [wikipedia](https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern) and in [SO documentation](http://stackoverflow.com/documentation/c%2b%2b/460/templates/2383/the-curiously-recurring-template-pattern-crtp#t=201608190715112457668) – rocambille Aug 19 '16 at 07:18
  • personally I have a problem with seeing the macro in Derived1, having to find the macro back and forth, especially because I was not the one writing it. – UmNyobe Aug 19 '16 at 08:03
  • 1
    @UmNyobe In a decent dev.env., the macro's definition should only be a mouse hover (or click at worst) away. A more descriptive name could also help (`DECLARE_NAME_GETTER`, perhaps?). And if it's part of a fundamental system, seeing the macro in basically every class will soon make you familiar. – Angew is no longer proud of SO Aug 19 '16 at 08:06
3

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" }
MSalters
  • 173,980
  • 10
  • 155
  • 350
  • Yes, although you have to then repeat yourself if you have multiple constructors. – The_Sympathizer Aug 19 '16 at 07:56
  • @mike4ty4: No, for that you use ctor forwarding in C++ : `Derived1(int foo) : Derived1() { // something with int foo` – MSalters Aug 19 '16 at 08:00
  • 5
    1. This can waste a lot of memory if you have many small objects. 2. This doesn't provide the static methods, so it doesn't really answer the question. – interjay Aug 19 '16 at 08:03
  • @interjay: True. I find that named objects tend to be high-level domain objects, which are fairly rare. – MSalters Aug 19 '16 at 08:07
  • @interjay: Then make the name a static variable. I like MSalters solution, for the very reason that it is really simple. I mean, that macro stuff other people do is clever, sure, but in my eyes "clever" just means "another possible source of error". – Aziuth Aug 19 '16 at 08:37
  • @Aziuth If the name is a static variable then you can't get the derived class's name from a base pointer. This solution is only "simple" because it doesn't actually solve the problem. – interjay Aug 19 '16 at 08:50
  • @interjay: I think it's fairly obvious what Aziuth meant, see edit. – MSalters Aug 19 '16 at 08:54
  • Another somewhat similar approach that I can think of, but haven't worked out: What about templates? Making Base a template over some variable that we can use as name and then deriving a class from an instance? Of course we have to see if we can do this cleverly AND simply, since std::string won't work as template parameter. Again, I haven't worked this out, just an idea. Might turn out to be garbage. – Aziuth Aug 19 '16 at 09:00
  • @MSalters Well, now you have about the same amount of code repetition as in OP's code, but also the drawback of more memory use and slower construction. I'd only use this solution if I needed to be able to change a specific instance's name. – interjay Aug 19 '16 at 09:01
1

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).
rubenvb
  • 74,642
  • 33
  • 187
  • 332
0

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
skypjack
  • 49,335
  • 19
  • 95
  • 187