QuEP 9: generic disposable objects

Luigi Ballabio

Abstract

A generalization of QuEP 4 is proposed which allows one to create temporary objects with small return-by-copy footprint.

Proposed implementation

A temporary array class was proposed in QuEP 4 which reduced the abstraction penalty related to returning temporary objects by copy. The analysis of such penalty and the mechanism used by the temporary array are not repeated here.

Rather, a generalization of such mechanism is introduced which allows it to be used with predefined types such as std::vector, albeit not to its full extent. Also, user-defined types can easily be modified so that they can take full advantage of this facility. Such modifications are purely additive, so that existing code would continue to work unaffected.

Such generalization is encapsulated in a template Temporary class which inherits from its template argument and redefines its copy constructor and assignment operator so that the instance contents are swapped rather than copied, as shown in the following implementation:

template <class T>
class Temporary : public T {                // Temporary is-a T
  public:
    // constructors
    Temporary() {}
    Temporary(const T& t) : T(t) {}         // deep copy as usual
    Temporary(Temporary& t) { swap(t); }    // swap this with t
    // assignment operators
    Temporary& operator=(const T& t) {
        T::operator=(t);                    // deep copy as usual
        return *this;
    }
    Temporary& operator=(Temporary& t) {
        swap(t);                            // swap this with t
        return *this;
    }
};

The above implementation relies on T exposing a method swap(T&) which swaps the contents of this and of the passed instance. Also, T must be assignable and have a default constructor. All such requirements are satisfied by all STL containers (see section 23.1 of the ANSI/ISO C++ standard) so that functions returning such types by value can benefit from the use of Temporary. For instance, a function implemented and called as:

std::vector<double> f(...) {
    std::vector<double> result;
    // calculate result
    return result;
}

std::vector<double> v = f(...);
implicitly performs three memory allocations and two copies of the vector elements (see QuEP 4 for details). Simply rewriting it as:
Temporary<std::vector<double> > f(...) {
    Temporary<std::vector<double> > result;
    // calculate result
    return result;
}

std::vector<double> v = f(...);
reduces the count to two allocations and one copy. In this case, we cannot do better since we cannot add to std::vector a new copy constructor taking a temporary.

For user-defined classes, this technique can be fully exploited by adding to such classes a swap method and by overloading copy constructor and assignment operator, as in:

class MyClass {
  public:
    MyClass();
    ... // other constructors
    MyClass(Temporary<MyClass>& x) { swap(x); }
    // the same for assignment operator
    ... // other methods
    void swap(MyClass& x) { swap data members }
    ...
};
This addition does not affect existing code and allows one to write and invoke functions as:
Temporary<MyClass> f(...) {
    Temporary<MyClass> result;
    // calculate result
    return result;
}

MyClass x = f(...);
Due to the overloading of MyClass::operator=, the cost of the above reduces to the allocation implied in the construction of the result. No further allocation or copy is performed.

A note on constructors

It must be noted that since constructors are not inherited, Temporary<T> does not have any particular constructor which T might implement besides the default one.

For instance, it would not be possible to write:

size_t size = 10;
Temporary<std::vector<double> > a(size);
since such constructor is not available. The above should instead be written:
size_t size = 10;
Temporary<std::vector<double> > a;
a.resize(size);
This also means that for Temporary<T> to be used, T must provide the means to incrementally construct a default-initialized T instance. E.g., for Temporary<Array> to be used, a resize method should be added to Array's public interface which is not currently available.

Conclusion

An implementation of generic disposable objects was proposed which reduces the abstraction penalty related to return-by-copy semantics. The presented technique can be successfully applied to both STL classes and used-defined types, the latter taking the most advantage of this penalty reduction.

Feedback

Feedback on the above proposal should be posted to the QuantLib-dev mailing list.