Ленивое создание объектов в C ++, или как сделать валидацию без затрат - PullRequest
2 голосов
/ 22 января 2009

Я наткнулся на этот замечательный пост о проверке параметров в C #, и теперь я задаюсь вопросом, как реализовать нечто подобное в C ++. Главное, что мне нравится в этом материале, это то, что он ничего не стоит, пока первая проверка не пройдет, так как функция Begin() возвращает null, а другие функции проверяют это.

Очевидно, что я могу добиться чего-то подобного в C ++, используя Validate* v = 0; IsNotNull(v, ...).IsInRange(v, ...), и каждый из них передает указатель v, плюс возвращает прокси-объект, для которого я дублирую все функции.

Теперь мне интересно, есть ли подобный способ добиться этого без временных объектов, пока первая проверка не пройдет. Хотя я предполагаю, что выделение чего-то вроде std::vector в стеке должно быть бесплатным (действительно ли это правда? Я подозреваю, что пустой вектор не выделяет в куче, верно?)

Ответы [ 4 ]

1 голос
/ 22 января 2009

Кроме того, что в C ++ нет методов расширения (что не позволяет легко добавлять новые проверки), это должно быть слишком сложно.

class Validation
{
    vector<string> *errors;
    void AddError(const string &error)
    {
       if (errors == NULL) errors = new vector<string>();
       errors->push_back(error);
    }

public:
    Validation() : errors(NULL) {}
    ~Validation() { delete errors; }

    const Validation &operator=(const Validation &rhs)
    {
        if (errors == NULL && rhs.errors == NULL) return *this;
        if (rhs.errors == NULL)
        {
            delete errors;
            errors = NULL;
            return *this;
        }
        vector<string> *temp = new vector<string>(*rhs.errors);
        std::swap(temp, errors);
    }

    void Check()
    { 
         if (errors)
             throw exception();
    }

    template <typename T>
    Validation &IsNotNull(T *value)
    {
        if (value == NULL) AddError("Cannot be null!");
        return *this;
    }

    template <typename T, typename S>
    Validation &IsLessThan(T valueToCheck, S maxValue)
    {
        if (valueToCheck < maxValue) AddError("Value is too big!");
        return *this;
    }

    // etc..

};


class Validate
{
public:
    static Validation Begin() { return Validation(); }
};

использование ..

Validate::Begin().IsNotNull(somePointer).IsLessThan(4, 30).Check();
0 голосов
/ 22 января 2009

Я рекомендую взглянуть на Boost.Exception , который обеспечивает в основном ту же функциональность (добавление произвольной подробной информации об исключении к одному объекту исключения).

Конечно, вам нужно написать несколько служебных методов, чтобы вы могли получить нужный интерфейс. Но будьте осторожны: разыменование нулевого указателя в C ++ приводит к неопределенному поведению, и нулевые ссылки даже не должны существовать. Таким образом, вы не можете вернуть нулевой указатель каким-либо образом, так как ваш связанный пример использует нулевые ссылки в методах расширения C #.

Для вещи с нулевой стоимостью: простое выделение стека довольно дешево, и объект boost::exception не выполняет само выделение кучи, но только если вы присоединяете к нему какие-либо объекты error_info <>. Таким образом, это не совсем ноль стоимость, но почти настолько дешево, насколько это возможно (один vtable-ptr для объекта исключения, плюс sizeof (intrusive_ptr <>)).

Следовательно, это должна быть последняя часть, где мы пытаемся оптимизировать дальше ...

0 голосов
/ 22 января 2009

См. Связанную статью: По-видимому, чрезмерная сложность создания объектов в C # настолько велика, что вызовы функций в сравнении бесплатны.

Я бы лично предложил синтаксис вроде

Validate().ISNOTNULL(src).ISNOTNULL(dst);

Validate () создает временный объект, который в основном представляет собой список проблем std ::. Пустые списки довольно дешевые (без узлов, размер = 0). ~ Validate сгенерирует, если список не пустой. Если профилирование показывает, что даже это слишком дорого, тогда вы просто меняете std :: list на список, свернутый вручную. Помните, указатель тоже объект. Вы не сохраняете объект, просто придерживаясь неудачного синтаксиса необработанного указателя. И наоборот, накладные расходы на обертывание необработанного указателя с хорошим синтаксисом - это просто цена времени компиляции.

PS. ISNOTNULL(x) будет #define для IsNotNull(x,#x) - подобно тому, как assert () выводит сбойное условие без необходимости повторять его.

0 голосов
/ 22 января 2009

Не могу сказать много по остальной части вопроса, но я действительно хотел указать на это:

Хотя я бы предположил, что распределение что-то вроде std :: vector на стек должен быть бесплатным (это на самом деле правда? Я подозреваю, что пустой вектор не выделяет куча, да?)

Нет. Вам все еще нужно распределить любые другие переменные в векторе (например, хранилище для длины), и я считаю, что это зависит от реализации, если они предварительно выделяют пространство для векторных элементов при построении. В любом случае, вы выделяете что-то, и, хотя его может не быть много, распределение никогда не бывает «бесплатным», независимо от того, происходит ли оно в стеке или куче.

Учитывая сказанное, я полагаю, что время, затрачиваемое на такие вещи, будет настолько минимальным, что будет действительно иметь значение, только если вы будете делать это много-много раз подряд.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...