问题
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