Search code examples
c++optimizationvalidationcode-duplication

C++: Is there an overhead of validating in a constructor if all data may already be valid?


I want to throw an exception in my constructor so that I don't have to deal with zombie objects. However I also want to provide a validation method upfront so that people can avoid "dealing with exceptions" where there is no cause to. In a GUI it's not exceptional to expect invalid data. However I also want to avoid code duplication and overhead. Is GCC / Microsoft Visual C++ compiler smart enough to remove this inefficiency of validating an input twice and if not, is there a subtle change that could please?

An example code block illustrating my point is below:

#include <string>
#include <exception>
#include <iostream>

using std::string;
using std::cout;
using std::endl;
using std::exception;


// a validation function
bool InputIsValid(const string& input) {
    return (input == "hello");
}

// a class that uses the validation code in a constructor
class MyObject {
    public:

    MyObject(string input) {
        if (!InputIsValid(input))    //since all instances of input
            throw exception();       //has already been validated
                                     //does this code incur an overhead
                                     //or get optimised out?

        cout << input << endl;       
    };

};

int main() {

    const string valid = "hello";

    if (InputIsValid(valid)) {
        MyObject obj_one(valid);
        MyObject obj_two(valid);
    }

    return 0;
}

I anticipate this may not be possibly if the object file for the class is generated alone as the object file has no way to ensure people will validate before calling the constructor, but when an application is compiled and linked together in a single application, is it possible please?


Solution

  • Is there an overhead of validating in a constructor if all data may already be valid?

    Yes, if the data is already validated, then you would incur the cost of validating it again

    Is GCC / Microsoft Visual C++ compiler smart enough to remove this inefficiency of validating an input twice and if not, is there a subtle change that could please?

    You could encapsulate your input in an object, and that object would remember the result of validation.

    template <typename INPUT_TYPE>
    class InputObject {
        INPUT_TYPE input_;
        bool valid_;
    public:
        typedef <typename VALIDATE>
        InputObject (INPUT_TYPE in, VALIDATE v) : input(in), valid_(v(in)) {}
        const INPUT_TYPE & input () const { return input_; }
        bool isValid () const { return valid_; }
    };
    
    typedef InputObject<std::string> MyInput;
    
    class MyObject {
    public:
        MyObject (const MyInput &input) {
            if (!input.isValid()) throw exception();
            //...
        }
    };
    

    The constructor to InputObject calls the validator function for you, and stores the result of validation in a flag. Then MyObject just checks the flag, and doesn't have to do the validation again.

    int main () {
        MyInput input("hello", InputIsValid);
    
        try {
            MyObject obj_one(input);
            MyObject obj_two(input);
        }
        catch (...) {
            //...
        }
    }
    

    As suggested by DeadMG, stronger type checking can be achieved by insisting that MyObject only accept validated input. Then, it would not need to throw in the constructor at all. Such a scheme could be accomplished by making NonValidatedInput and ValidatedInput two different types.

    template <typename> class NonValidatedInput;
    
    template <typename T>
    class ValidatedInput {
        friend class NonValidatedInput<T>;
        T input;
        ValidatedInput (const T &in) : input(in) {}
    public:
        operator const T & () const { return input; };
    };
    
    template <typename T>
    class NonValidatedInput {
        T input;
    public:
        operator ValidatedInput<T> () const { return ValidatedInput<T>(input); }
        template <typename V>
        NonValidatedInput (const T &in, V v) : input(in) {
            if (v(input) == false) throw exception();
        }
    };
    

    NonValidatedInput accepts non-validated input, and performs the validation, and can be converted to a ValidatedInput object if the validation succeeds. If the validation fails, NonValidatedInput throws an exception. Thus, MyObject has no need to check for validation at all, because it's constructor only accepts ValidatedInput.

    typedef ValidatedInput<std::string> MyInput;
    
    class MyObject {
    public:
        MyObject (MyInput input) {
            std::string v = input;
            std::cout << v << std::endl;
        }
    };
    
    int main () {
        try {
            MyInput input = NonValidatedInput<std::string>("hello", InputIsValid);
            MyObject obj_one(input);
            MyObject obj_two(input);
        }
        catch (...) {
            //...
        }
    }
    

    The type safety here is quite strong, because only NonValidatedInput can create a ValidatedInput, and only if the validation succeeded.