问题
To be honest, I don't really know, how to ask this question, so please don't be mad :)
Anyway, I want to have the mutators (setters) in my class to return this
to allow for jQuery-like a.name("something").address("somethingelse");
I have a parent class (Entity
) and several childclasses (Client, Agent etc.
). The mutators for most things are inherited from the Entity
class (like name or address), but they return an Entity
object, so I can't call Client mutators on them.
In other words:
// name mutator
Entity& Entity::name( const string& name ) {
// [...] checks
_name = name;
return *this;
}
// budgetRange mutator
Client& Client::budgetRange( const long int& range ) {
// [...] checks
_budgetRange = range;
return *this;
}
then when I call it:
Client a; a.name("Dorota Adamczyk").budgetRange(50);
The compiler (logically) says, that the Entity object has no budgetRange member (because name returns an Entity, not a Client).
My question now is: how could I implement something like this? I thought about overloading all the Entity functions in the childclasses but that wouldn't be nice and would be against the idea of inheritance :)
Thank you in advance for your ideas :D
回答1:
You should use the CRTP.
template<class Derived>
class Entity
{
Derived* This() { return static_cast<Derived*>(this); }
public:
Derived& name(const string& name)
{
...
return *This();
}
};
class Client : public Entity<Client>
{
public:
Client& budgetRange(const long& range)
{
...
return *this;
}
};
If you want to use virtual functions, you can also add abstract base class, like this:
class AbstractEntity
{
public:
virtual void foo() = 0;
virtual ~AbstractEntity();
};
template<class Derived>
class Entity : AbstractEntity
{...};
回答2:
The "curiously recursive template" pattern could help here; make the base class a template, parametrised by the derived class, along the lines of:
template <typename Derived>
struct Entity {
Derived & name(std::string const & name) {
// stuff
return static_cast<Derived&>(*this);
}
};
struct Client : Entity<Client> {
Client & budget(long range) {
// stuff
return *this;
}
};
Client().name("Mike").budget(50); // should compile
This will only work if all your types inherit directly from Entity
. If you need the types to be polymorphic (i.e. all share a common base class), then you'll need to add another non-template base class, and have Entity
inherit from that.
回答3:
Now that nearly everything has already been said, I want to add a piece of answer that allows one to use the CRTP over multiple levels of inheritance:
The above CRTP implementations break when one wants to inherit from Client
, since Derived
will refer to Client
. In case you want to be able to carry the named parameter idiom over multiple levels of inheritance using CRTP pattern, you need to code your classes like so
template<class Derived>
class Entity_T
{
protected:
Derived* This() { return static_cast<Derived*>(this); }
public:
Derived& name(const string& name)
{
...
return *This();
}
};
template<class Derived>
class Client_T : public Entity_T<Derived>
{
Derived& budgetRange(const long& range)
{
...
return *This();
}
};
To provide the user a template-free version of Client_T
add
class Client : public Client_T<Client> {};
Whether or not this is worth the enlarged code base is entirely up to you. Note, that I haven't compiled the code above.
来源:https://stackoverflow.com/questions/8575948/c-function-in-parent-return-child