Method chaining
In this post I will talk about a simple but sometimes useful programming technique - method chaining. I will also talk about the potential pitfall associated with its use.
To make it more interesting, first a small test.
1. What is method chaining? The essence of this technique?
2. How is it implemented in C ++?
3. Can you come up with a potentially dangerous situation related to the use of this technique?
Sometimes, when using or writing large classes, it becomes necessary to call several methods of an object of this class in a row. Usually it looks like this:
Reception of method chaining allows to reduce this code. To do this, in each of our methods, we will return a link to our object and line up the calls.
As far as I know, this technique is loved in Java. In C ++, it is not very popular and I in no way urge it to be used, but I think it won’t hurt to know about it.
Strictly speaking, what I will describe below refers not so much to method chaining as to the order in which arguments and function calls are calculated, but nevertheless, when using the “call chain”, these rules at first glance may work unexpectedly. So.
This code compiles without warning or error. But the execution results may vary on different compilers.
The fact is that although the standard guarantees that process () will be called before print_result (), it is not guaranteed that the argument to the print_result function will be evaluated after process () is executed. Accordingly, sometimes as a result of the execution of this code, “2” may be output.
Test
To make it more interesting, first a small test.
1. What is method chaining? The essence of this technique?
2. How is it implemented in C ++?
3. Can you come up with a potentially dangerous situation related to the use of this technique?
Theory
Sometimes, when using or writing large classes, it becomes necessary to call several methods of an object of this class in a row. Usually it looks like this:
class worker
{
public:
void set_data(const data_t& d);
void process();
void send_result();
void print_log();
...
};
void foo()
{
worker w;
w.set_data(data_t{});
w.process();
w.send_result();
w.print_log();
...
}
Reception of method chaining allows to reduce this code. To do this, in each of our methods, we will return a link to our object and line up the calls.
class worker
{
public:
worker& set_data(const data_t& d){...; return *this;}
worker& process(){...; return *this;}
worker& send_result(){...; return *this;}
worker& print_log(){...; return *this;}
...
};
void foo()
{
worker w;
w.set_data(data_t{}).process().send_result().print_log();
...
}
As far as I know, this technique is loved in Java. In C ++, it is not very popular and I in no way urge it to be used, but I think it won’t hurt to know about it.
Underwater rock
Strictly speaking, what I will describe below refers not so much to method chaining as to the order in which arguments and function calls are calculated, but nevertheless, when using the “call chain”, these rules at first glance may work unexpectedly. So.
struct worker
{
worker& process(int& i)
{
i = 185;
return *this;
}
worker& print_result(const int& i)
{
std::cout <<"result: "<< i << std::endl;
return *this;
}
};
int main()
{
int data = 0;
worker w;
w.process(data).print_result(data+2);
}
This code compiles without warning or error. But the execution results may vary on different compilers.
The fact is that although the standard guarantees that process () will be called before print_result (), it is not guaranteed that the argument to the print_result function will be evaluated after process () is executed. Accordingly, sometimes as a result of the execution of this code, “2” may be output.