Lifetime Methods

Lifetime methods are language-defined methods for affecting an object’s lifetime, such as expressing how to move it, destroy it, putting them into particular memory states etc. (the C++ equivalent are destructors, move constructors, move assignment operators, etc.). These methods can be identified by the fact that their names begin with two underscore characters; no other methods may begin with two underscores.

By default, lifetime methods will be auto-generated by the compiler to provide expected behaviour, however in some cases developers may wish to provide their own implementations for custom behaviour or eliminate any liveness prefix bytes.

Some lifetime methods call other lifetime methods to complete their function. For example, at the end of a destructor code is emitted to call __setdead and hence put the object into a dead state; this avoids running the destructor on it again.

All lifetime methods are noexcept and hence cannot throw exceptions in any case.

islive

bool __islive() const noexcept;

__islive returns a boolean: true means a live state and false means a dead or invalid state.

As discussed below for __setdead, this is a conservative estimate:

  • If we are in a live state, then __islive will always return true.
  • If __islive returns true, we are not guaranteed to be in a live state.

setdead

void __setdead() noexcept;

__setdead puts an object memory slot into a dead state, by writing that particular state into the memory. __setdead is not recursive, so it doesn’t put the member objects into a dead state.

Note that __setdead is allowed, for practical reasons, to leave the memory unchanged. This is usually the case when no dead state is required because the object does not have a custom move method or destructor. For example:

class ExampleClass(int value) {
        static create = default;
}

The only purpose of dead states is to ensure we only invoke user-specified move methods and destructors at the correct times. So if neither exists we have no need for dead states and so __setdead becomes a no-op.

Hence it’s possible to invoke __setdead on a memory slot, but to still have __islive subsequently return true.

Further, combined with the fact __setdead is not recursive, calling __setdead on an object is not sufficient to ensure that any subsequent calls to __move or __destroy have no effect.

move

T __move() noexcept;

__move returns an object moved from the current memory slot. Due to the Loci ABI __move will receive two pointers: the this pointer (the source) and a return value pointer (the destination).

__move is a recursive operation, so member objects will be moved to the destination. This method consists of an outer part and an inner ‘user’ part; only the latter can be customised.

The outer part of the __move method consists of:

if (object.__islive()) {
        [user __move code]
        object.__setdead();
} else {
        // Set the destination to dead state.
        movedest.__setdead();
}

This structure means that the user __move code is only executed when the object is in a live state, which means your __move implementation doesn’t need to make any checks itself. It also means you don’t have to put the source object into a dead state at the end of the move operation.

destroy

void __destroy() noexcept;

__destroy releases the resources assigned to an object and puts the object into a dead state (by calling __setdead). __destroy is a recursive operation, so member objects will also have their __destroy methods invoked.

This method consists of an outer part and an inner ‘user’ part; only the latter can be customised.

The outer part of the __destroy method consists of:

if (object.__islive()) {
        [user __destroy code]

        // Call member destructors in REVERSE order.
        members[N].__destroy();
        members[N-1].__destroy();
        // ...
        members[1].__destroy();
        members[0].__destroy();

        object.__setdead();
}

This structure means that the user __destroy code is only executed when the object is in a live state, which means your destructor implementation doesn’t need to make any checks itself. It also means you don’t have to put the source object into a dead state at the end of the destructor.

isvalid

bool __isvalid() const noexcept;

__isvalid returns a boolean: true means a valid (live or dead) state and false means an invalid state.

Note

This method is not generated by default; you need to specify it manually to indicate that an invalid state exists.

setinvalid

void __setinvalid() noexcept;

__setinvalid puts an object in any state into an invalid state.

Note

This method is not generated by default; you need to specify it manually to indicate that an invalid state exists.