Skip to content

← Copy, Move, and the Rule of Zero step 5 of 5

Medium Primitives

What is left after you move from it

Moving from a C++ object does not end its lifetime. You can still use it, subject to the guarantees of its type and the requirements of each operation. Standard-library types generally remain in a valid but unspecified state unless the operation promises something more specific. User-defined types need their own documented guarantees.

Valid means the type's rules still hold. Unspecified means you must not assume a particular value, such as the contents it had before the move. It does not mean that every subsequent read is unsafe.

Check the current state before using it

For a vector in a valid but unspecified state, this is safe:

if (!a.empty()) {
    int first = a.front();  // the non-empty check satisfies the requirement
    // use first
}
a.clear();                 // safe even if already empty
a = {1, 2, 3};             // assign known contents

Calling a.front() or a[0] without establishing that an element exists would be unsafe. Calling a.empty() or a.size() is allowed; the result describes its current state, not necessarily its state before the move.

Some operations promise more

Move construction from a source What happens to the source
std::unique_ptr<T> It becomes null
std::optional<T> Its presence flag is unchanged; if it held a value, that value is moved from

An empty optional stays empty. An engaged optional stays engaged, even if moving its contained value changes that value. Read the guarantee for the particular type and operation rather than assuming every source becomes empty.

When you need a fact about an object's original contents, capture it before moving, or inspect the destination that received them.

Your task

Batch drain(std::vector<Record>& records);

Each record has a std::unique_ptr<std::vector<int>> named samples. Transfer each pointer to a local owner, append its integers to Batch::collected in record order, and count non-null pointers in Batch::with_samples. Keep the records in place; their pointers become null.

A non-null pointer to an empty vector counts as one record with a sample list, even though it contributes no integers. A null pointer contributes nothing and is not counted.

The starter checks record.samples after moving it into taken. That check is safe, but always false: moving the unique pointer made the source null. Fix the count by checking before the move or checking taken afterwards.

In the test input, an empty row represents a null pointer. For example, [[1, 2], [], [3]] produces collected values [1, 2, 3] and a count of 2. This input encoding does not distinguish an owned empty list from a null pointer; the function should still handle either case correctly.