jeudi 25 août 2016

C++ Use Function Preconditions Or Wrapper Classes with Invariants?

I find myself writing a lot of functions that begin with many preconditions, and then I have to figure out how to handle all the invalid inputs and write tests for them.

Note that the codebase I work in does not allow throwing exceptions, in case that becomes relevant in this question.

I am wondering if there is any C++ design pattern where instead of having preconditions, input arguments are passed via wrapper classes that guarantee invariants. For example suppose I want a function to return the max value in a vector of ints. Normally I would do something like this:

// Return value indicates failure.
int MaxValue(const std::vector<int>& vec, int* max_value) {
    if (vec.empty()) {
        return EXIT_FAILURE;
    }
    *max_value = vec[0];
    for (int element : vec) {
        if (element > *max_value) {
            *max_value = element;
        }
    }
    return EXIT_SUCCESS;
}

But I am wondering if there is a design pattern to do something like this:

template <class T>
class NonEmptyVectorWrapper {
  public:
    static std::unique_ptr<NonEmptyVectorWrapper>
             Create(const std::vector<T>& non_empty_vector) {
        if (non_empty_vector.empty()) {
             return std::unique_ptr<NonEmptyVectorWrapper>(nullptr);
        }
        return std::unique_ptr<NonEmptyVectorWrapper>(
            new NonEmptyVectorWrapper(non_empty_vector));
    }

    const std::vector<T>& vector() const {
        return non_empty_vector_;
    }

  private:
    // Could implement move constructor/factory for efficiency.
    NonEmptyVectorWrapper(const std::vector<T>& non_empty_vector)
            : non_empty_vector_(non_empty_vector) {}
    const std::vector<T> non_empty_vector_;
};

int MaxValue(const NonEmptyVectorWrapper<int>& vec_wrapper) {
    const std::vector<int>& non_empty_vec = vec_wrapper.vector();
    int max_value = non_empty_vec[0];
    for (int element : non_empty_vec) {
        if (element > max_value) {
            max_value = element;
        }
    }
    return max_value;
}

The main pro here is that you avoid unnecessary error handling in the function. A more complicated example where this could be useful:

// Finds the value in maybe_empty_vec which is closest to integer n.
// Return value indicates failure.
int GetValueClosestToInt(
    const std::vector<int>& maybe_empty_vec,
    int n,
    int* closest_val);

std::vector<int> vector = GetRandomNonEmptyVector();
for (int i = 0; i < 10000; i++) {
    int closest_val;
    int success = GetValueClosestToInt(vector, i, &closest_val);
    if (success) {
        std::cout << closest_val;
    } else {
        // This never happens but we should handle it.
    }
}

which wastefully checks that the vector is non-empty each time and checks for failure, versus

// Returns the value in the wrapped vector closest to n.
int GetValueClosestToInt(
    const NonEmptyVectorWrapper& non_empty_vector_wrapper,
    int n);

std::unique_ptr<NonEmptyVectorWrapper> non_empty_vector_wrapper =
    NonEmptyVectorWrapper::Create(GetRandomNonEmptyVector());
for (int i = 0; i < 10000; i++) {
    std::cout << GetValueClosestToInt(*non_empty_vector_wrapper, i);
}

which can't fail and gets rid of the needless input checking.

Is this design pattern a good idea, is there a better way to do it, and is there a name for it?

Aucun commentaire:

Enregistrer un commentaire