13

I recently used a library that allows the following type of syntax:

MyClass myObject;
myObject
    .setMember1("string value")
    .setMember2(4.0f)
    .setMember3(-1);

Obviously this is accomplished by having the setters return a MyClass & type; something like return *this. I like the way this code looks, but I don't see it a lot. When that happens I'm usually suspicious as to why.

So, is this a bad practice? What are some of the implications of doing it like this?

James McNellis
  • 348,265
  • 75
  • 913
  • 977
Joe
  • 4,585
  • 3
  • 37
  • 51

8 Answers8

6

This is sometimes referred to as the Named Parameter Idiom or method chaining. This isn't bad practice, it can aid readability. Consider this example lifted from the C++ FAQ

File f = OpenFile("foo.txt")
            .readonly()
            .createIfNotExist()
            .appendWhenWriting()
            .blockSize(1024)
            .unbuffered()
            .exclusiveAccess();

The alternative would be to use positional arguments to the OpenFile method, requiring the programmer to remember the position of each argument.

Sam Miller
  • 23,808
  • 4
  • 67
  • 87
  • 3
    Or use Boost Parameter, which looks even slicker. – Bill Prin Feb 04 '11 at 15:41
  • -1 The OP's code is not the named parameter idiom. What you show (from the FAQ) is an example of named parameter idiom. The difference is what can be set: attributes of the final object you're constructing (very bad, you really don't want that), or attributes of argument pack (good). – Cheers and hth. - Alf Feb 04 '11 at 16:33
  • 5
    I should perhaps expand on the downvote comment. The named parameters idiom is good: it yields **single-phase construction**, where after a constructor has executed successfully, object is fully initialized with working class invariant. The OP's code instead uses **two-phase construction** or **multi-phase construction**, which is Bad because after C++ constructor finished successfully, you still don't have a ready-to-use object. Bjarne wrote at about it in appendix to TCPPPL, available as PDF from his site. In addition OP's code exposes attributes willy-nilly. So, NPI = good, OP's code = bad. – Cheers and hth. - Alf Feb 04 '11 at 17:54
6

Some people call this fluent programming (or a fluent interface). Others call it a mess.

I tend somewhat toward the latter camp. In particular, my experience has been that in many cases, people writing code this way depend on the "fluent interface" for quite a bit of initializing an object. In other words, despite the disguise, it's still two-step initialization. Likewise, although it's probably avoidable in many cases, it frequently seems to result in quite a bit of what should be entirely private to the class being made publicly modifiable via manipulators.

Personally I prefer that objects are immutable after creation. That's clearly not always possible, and in some cases you can't even come very close. Nonetheless, the more of an object's internals that you make open to outside manipulation, the less certain you become about that object maintaining a coherent state (and, typically, the more work you have to do to maintain a coherent state).

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • Correct me if I'm wrong, but it sounds like you're against setters in general (fluent or not) :-) – Joe Feb 04 '11 at 16:05
  • It is easy to use fluent interface and immutable objects. The constructor for the immutable takes an object parameter. That object is setup using a fluent interface. – Zan Lynx Feb 04 '11 at 17:12
  • @Zan Lynx: Yes, with various legerdemain and indirection, you can cover up some of their problems, but it still strikes as more of a hack than a technique (though, in fairness, many other people say the same about things of which I approve...) – Jerry Coffin Feb 04 '11 at 17:25
4

Your example is not the named parameters idiom.

With the named parameters idiom, the chainable setters set attributes of an argument (parameter) pack.

Your code instead has a bunch of setters for modifying the final object you're constructing, called two-phase construction.

In general two-phase construction is just Bad™, and two-phase construction implemented by exposing attributes to client code, as in your example, is Very Bad™.

For example, you do not, in general, want to be able to modify a file object's attributes, once that object has been constructed (and possibly file opened).

Cheers & hth.,

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
2

It's a common practice. The overloading of operator= implies to do so to chain calls:

class Foo {

public:
   Foo& operator=(const Foo& f) { 
      if (this != &f) { // check for self-assignment
         // do some stuff...
      }
      return *this;
   }

};

This code allows to do things like:

Foo a, b, c;
a = b = c;

Please note that checking for self-assignment is compulsory because you often have to deallocate stuffs in the current object, so allowing a = a would break your code.

Following @Fred Nurk's comment, I'd like to add that you should have a look at the Copy-and-Swap idiom in order to avoid code duplication and issue an exception-free code.
Have a look at the links hereunder for more information:

What is the copy-and-swap idiom?
http://gotw.ca/gotw/059.htm

Community
  • 1
  • 1
jopasserat
  • 5,721
  • 4
  • 31
  • 50
  • 4
    The copy-swap idiom handles (the rare) self-assignment case in a cleaner way. – Fred Nurk Feb 04 '11 at 15:51
  • +1, there are also the canonical `operator<<` and `operator>>` for streaming. Chaining is not new, definitely, and well-endorsed by the Standard Library. – Matthieu M. Feb 04 '11 at 15:55
  • +1 for your comment @Fred Nurk. But can we apply the copy-swap idiom every time? For example when a class needs to reallocate larger memory in certain cases? – jopasserat Feb 04 '11 at 16:01
  • 1
    Nothing works always. :) There are cases where copy-swap doesn't work, but I will still use copy-swap by default until I see that a class is one of those cases. – Fred Nurk Feb 04 '11 at 16:03
  • @Fred Nurk: updated my answer to take your comment into account – jopasserat Feb 04 '11 at 18:00
1

There's no problem with this style. The only downside is that you can't use the return value for more typical purposes, like returning the result of the function.

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
  • see @Jerry Coffin's answer, and my answer. Two-phase construction is a monstrosity, exposing attributes willy-nilly is also, combined (as here) they're, well, whatever, monstrosity2. One should instead use e.g. the Named Parameters Idiom. – Cheers and hth. - Alf Feb 04 '11 at 16:56
  • @Alf, thanks for pointing out the difference - I've seen the Named Parameters idiom before but never used it, thus its subtleties were lost to me. I wouldn't condemn two-phase construction or exposed attributes as harshly as you, but I agree that they're not often the best solution. – Mark Ransom Feb 04 '11 at 22:04
1

It's called a fluent api. It's not bad practice, just a different style of programming.

The biggest drawbacks (IMO) would be that since you are returning a reference to yourself, you can't return anything else and it can be difficult to debug fluent statements since they are seen by the compiler as one giant "line" of code.

Eric Petroelje
  • 59,820
  • 9
  • 127
  • 177
1

I'm not sure if it's considered bad practice or not, but one implication is that you can no longer return error codes, so you're either forced to use exceptions or ugly error objects passed in by reference. Exceptions are sometimes the right solution, but often aren't since they can be expensive to throw and are disabled on some platforms.

I really think it's a stylistic thing though, and because of C++'s close relationship with C both in syntax and culture, many C++ programmers like error codes and so will prefer returning them instead of returning a reference. But I've seen return of *this often as well and I don't think it's bad practice.

Bill Prin
  • 2,498
  • 1
  • 20
  • 27
1

Not bad practice, in fact you see it often with output streams in order to concatenate multiple strings and values.

Only disadvantage that I see is that it prevents you from returning anything else, though if it's a set method, it shouldn't matter.

Neil
  • 5,762
  • 24
  • 36