Is there a name for this:
class A
{
A* setA()
{
//set a
return this;
}
A* setB()
{
//set b
return this;
}
};
so you can do something like this:
A* a = new A;
a->setA()->setB();
Are there any drawbacks to using this? Advantages?
It's known as method chaining (FAQ link), and is more commonly done with references, not pointers.
Method chaining is strongly associated with the Named Parameter Idiom (FAQ link), as I now, after posting an initial version of this answer, see that Steve Jessop discusses in his answer. The NPI idiom is one simple way to provide a large number of defaulted arguments without forcing complexity into the constructor calls. For example, this is relevant for GUI programming.
One potential problem with the method chaining technique is when you want or need to apply the NPI idiom for classes in an inheritance hierarchy. Then you discover that C++ does not support covariant methods. What that is: when you let your eyes wander up or down the classes in a chain of class inheritance, then a covariant method is one whose definition involves some type that to your wandering eye varies in specificity in the same way as the class it’s defined in.
It is about the same problem as with defining a clone
method, which has the same textual definition in all classes, but must be laboriously repeated in each class in order to get the types right.
Solving that problem is hard without language support; it appears to be an inherently complex problem, a kind of conflict with the C++ type system. My “How to do typed optional arguments in C++98” blog post links to relevant source code for automating the generation of covariant definitions, and to an article I wrote about it in Dr. Dobbs Journal. Maybe I'll revisit that for C++11, or sometime, because the complexity and possible brittleness may appear as a larger cost than it’s worth…
I've heard it called something like "method chaining" before, but I wouldn't call it a design pattern. (Some people also talk about implementing a "fluent interface" using this - I'd never seen it called that before though, but Martin Fowler seems to have written about it a while back)
You don't lose much by doing this - you can always ignore the return result quite happily if you don't want to use it like that.
As to is it worth doing I'm less sure. It can be quite cryptic in some circumstances. It is however basically required for things like operator<<
for stream based IO though. I'd say it's a call to be made on how it fits in with the rest of the code - is it expected/obvious to people reading it?
(As Steve Jessop pointed out this is almost always done with references though, not pointers)
Another common-ish use is with "parameter objects". Without method chaining, they're quite inconvenient to set up, but with it they can be temporaries.
Instead of:
complicated_function(P1 param1 = default1, P2 param2 = default2, P3 param3 = default3);
Write:
struct ComplicatedParams {
P1 mparam1;
P2 mparam2;
P3 mparam3;
ComplicatedParams() : mparam1(default1), mparam2(default2), mparam3(default3) {}
ComplicatedParams ¶m1(P1 p) { mparam1 = p; return *this; }
ComplicatedParams ¶m2(P2 p) { mparam2 = p; return *this; }
ComplicatedParams ¶m3(P3 p) { mparam3 = p; return *this; }
};
complicated_function(const ComplicatedParams ¶ms);
Now I can call it:
complicated_function(ComplicatedParams().param2(foo).param1(bar));
Which means the caller doesn't have to remember the order of parameters. Without the method chaining that would have to be:
ComplicatedParams params;
params.param1(foo);
params.param2(bar);
complicated_function(params);
I can also call it:
complicated_function(ComplicatedParams().param3(baz));
Which means that without having to define a tonne of overloads, I can specify just the last parameter and leave the rest at default.
The final obvious tweak is to make complicated_function
a member of ComplicatedParams
:
struct ComplicatedAction {
P1 mparam1;
P2 mparam2;
P3 mparam3;
ComplicatedAction() : mparam1(default1), mparam2(default2), mparam3(default3) {}
ComplicatedAction ¶m1(P1 p) { mparam1 = p; return *this; }
ComplicatedAction ¶m2(P2 p) { mparam2 = p; return *this; }
ComplicatedAction ¶m3(P3 p) { mparam3 = p; return *this; }
run(void);
};
ComplicatedAction().param3(baz).run();
One downside is that if you derive a class from A, say like this:
class Foo : public A
{
public:
Foo *setC()
{
// set C
return this;
}
};
then the order you call the setters is important. You'll need to call all the setters on Foo first: For example, this won't work:
Foo f=new Foo();
f->setA()->setC();
Whereas this will:
Foo f=new Foo();
f->setC()->setA();
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With