[Development] Setters: Passing by value or const reference?

Marc Mutz marc.mutz at kdab.com
Wed Apr 25 15:07:24 CEST 2012


Hi Olivier,

On Wednesday April 25 2012, Olivier Goffart wrote:
> In Qt, we traditionally uses const reference as parameter for all our
> setters. This is indeed probably the most efficient thing to do for non-POD
> in C++98 since it avoids copy.  But the situation is different in C++11
[...]
> We could implement a move setter (Foo::setText(QString&&)).  But that would
> mean  duplication, and it could not be inline (access to d_ptr) so binary
> incompatibility between Qt compiled with or without C++11.

The rvalue overloads could only be used by a C++11 compiler, and it would be 
natural for a C++11 app to require a C++11-compiled Qt. There's no such thing 
as binary compatibility between compilations using different flags anyway. 
Indeed, most move constructors (excepting the few you implemented) cannot be 
implemented inline (because they require the definition of ~FooPrivate), so 
you already have an C++11/98 BiC issue once you've implemented all the shared 
classes' move constructors (I did, does anyone else work on this, btw?).

Also, as you noted yourself, it's probably too late to change all setters. 
However, an rvalue reference overload can be added in a BC way, in 5.1.

Taken together, this to me suggests to use rvalue overloads instead of 
pass-by-value. The resulting code duplication can and should be factored in 
the implementation, of course.

> The solution is actually much simpler:
>
> void Foo::setText(QString text) {
> 	d_ptr->text = std::move(text);  // no copy
> }

Most setters look not like the one you quoted, but like this:

void Foo::setText(const QString &text) {
    if ( text == d->text ) return;
    d->text = text; // copy only if needed
    // do something expensive, like repaint, or emit a signal
}

Here, a copy is taken only if you don't early out. When you overload setText() 
for const-reference and rvalue-reference instead, you only incur a copy iff 
it's needed. Not sure that's a problem. Also not sure if slicing could become 
a problem in a pass-by-value world, but const-& and && are both real 
references, so they don't slice.

> foo->setText(tr("hello world")); // no copy here, this is a move.
>
> Now you don't have any copy: you saved two atomic operations (increment,
> decrement) and generated less code.
> If you pass something that is not a temporary, you still have only one
> copy, but on the caller.
>
>>
> You notice the use of std::move,  which we can use only in C++11.
> Hence the introduction of a qMove macro[1]
> https://codereview.qt-project.org/24444
> Which would lead to that pattern:
>
> void Foo::setText(QString text) {	d_ptr->text = qMove(text); }

You should use d_ptr->text.swap(text) instead, that's just as fast (maybe even 
faster) and requires no C++11 :)

Talking of swap: I have a half-finished patch that makes Q_DECLARE_SHARED 
implement the two op='s with (copy)-swap (and adds swap() to classes that 
would benefit from it, but don't have it yet).

Foo &Foo::operator=(const Foo &other)
{ if ( this != &other ) { Foo copy(other); swap(other); } return *this; }
Foo &Foo::operator=(Foo &&other)
{ other.swap(*this); return *this; }

I'll try to get it into a reviewable state.

Thanks,
Marc

-- 
Marc Mutz <marc.mutz at kdab.com> | Senior Software Engineer
KDAB (Deutschland) GmbH & Co.KG, a KDAB Group Company
www.kdab.com || Germany +49-30-521325470 || Sweden (HQ) +46-563-540090
KDAB - Qt Experts - Platform-Independent Software Solutions



More information about the Development mailing list