[Development] What's the status of a moved-from object?

Mutz, Marc marc at kdab.com
Mon May 20 20:18:32 CEST 2019


On 2019-05-20 17:16, Thiago Macieira wrote:
> On Monday, 20 May 2019 05:51:49 PDT Mutz, Marc via Development wrote:
>> Or maybe we don't disagree at all and Thiago would accept allocating
>> memory (or, by extension, anything that's noexcept(false)) as a very
>> good reason to have a nullptr d?
> 
> I hadn't thought of noexcept, but let's be clear: yes, move 
> constructors must
> be noexcept. That might be the good reason why it can't reset to a 
> valid
> state.

What a feat it would be if Qt celebrated the 10th anniversary of the 
publication of Elements of Programming with embracing the 
partially-formed state :)

So, we seem to agree that moved-from objects may have d == nullptr. In 
the following, let this be our premiss.

Where we still, maybe, disagree, is whether d == nullptr is a valid 
state. The difference is whether member functions other then destruction 
and assignment check for a nullptr d. I'd propose that on classes under 
the above premiss, Q_D contains Q_ASSERT(d). This, I think, strikes the 
best balance between safety and speed. I think it's important to make 
using moved-from objects an error, because it is. Trying to pamper it 
over by assigning some magic meaning to a nullptr d is going to cause 
more problems than it solves (std::variant::valueless_by_exception, 
anyone?).

If and when we accept this as policy going forward, the next question 
becomes: What does the default ctor do? I fully realize that after 
decades of constructing magic values at default construction time, Qt is 
in no position to make default constructors set d = nullptr. For 
existing classes, the documented behaviour of the default constructor is 
to establish a particular state (cf. QPen). But at least for new 
classes, we should really think about having the default ctor do nothing 
more than d = nullptr. And maybe deprecate the default constructor's 
value for Qt 7 or 8.

Why is a almost-no-op default ctor so important? Performance, yes. 
Noexcept, yes. And there's need for this. Grep Qt::Initialization. 
People _need_ the default ctors to do less work. Let's give it to them.

But I'd like to focus on something else here: Qt likes to pride itself 
for good API design. So let's look at it from that angle:

    QPen pen1 = ~~~;
    QPen pen2 = std::move(pen1);
    QPen pen3;

What's the state of 'pen1' now? Well, QPen is actually a class that sets 
d = nullptr in the moved-from object. So pen1 does not represent a 
value. This behaviour is in Qt for ten(!) minor releases now. I didn't 
find a bugreport about that. What about pen3? Well, black, solid, 
width=1 pen. Why do I know? Because I looked it up. It makes the code 
hard to understand, because for each class, you need to know what the 
default constructor does. Worse: We don't know what the intent of the 
developer is here. Does she want to create (a) a black pen, or does she 
(b) simply want to have _a_ pen, so she can specify later what it should 
be? We don't know. We need to scan the code further.

What is striking here is that a moved-from pen is _different_ than a 
default-constructed one. Wouldn't it be ore intuitive if the states were 
the same?

Under Stepanov's model

    QPen pen1 = Qt::black; // clearly (a)
    QPen pen2;             // clearly (b)

it's 100% clear that pen2 is just there to be assigned to later. So, 
(b). It cannot possibly be (a), because a default-constructed QPen 
object does not represent a valid pen. Furthermore, when pen1 is moved 
from, it will end up in the same state as pen2 - partially-formed.

Can it get any simpler?

Thanks,
Marc


More information about the Development mailing list