Шаблон класс для инкапсуляции спецификации таблицы с помощью опции


Пару лет назад я написал пару классов шаблоны для инкапсуляции технические характеристики, которые имеют одну или более минимальной, типичной, и/или максимального значения, например, на даташит такой один на 741 ОУ:

741 datasheet examples Этот кодекс требовал от меня, чтобы заново изобретать колесо немного, так как я использовал старый компилятор, и я не обнаружен boost::optional/std::optional еще. Я переписать этот код, чтобы использовать один класс шаблон Specв C++11 и C++14 функций, и boost::optional или std::optional. Я также включил некоторые дополнительные утилиты (установка Spec в std::ostreamзажимные Spec ценности, вычислительной спецификаций ограничений, основанных на стоимости и % толерантность и т. д.). Я еще не была добавлена поддержка guardbands как и в предыдущей реализации, но я планирую сделать это позже.

Вот реализация с помощью boost::optional. Мой компилятор в Visual студии 2017 (которая может использовать C++17 по std::optional), но я только на C++14 особенности в проекте это будет использоваться. Я закомментированный код ниже, чтобы использовать std::optional вместо:

#ifndef SPEC_H
#define SPEC_H

#include <ostream>
#include <stdexcept>

// To use std::optional, if supported by compiler
//#include <optional>

// To use boost::optional
#include <boost/none.hpp> // boost::none_t
#include <boost/optional.hpp>

// If using std::optional
/*typedef std::nullopt_t nullspec_type;
inline constexpr nullspec_type nullspec = std::nullopt;*/

// Using boost::optional
/** \brief Type definition for an empty class type which indicates that a Spec value is undefined. */
typedef boost::none_t nullspec_type;

/** \brief A constant which can be used as an argument to a Spec constructor with a nullspec_type
parameter.
*/
const nullspec_type nullspec = boost::none;

/** \brief Exception class for attempts to access non-existent Spec values. */
class bad_spec_access : public std::logic_error {
public:
    /** \brief Constructs a Spec bad access error */
    bad_spec_access(const std::string& s) : std::logic_error(s) {}
};

/** \brief Encapsulates the specified minimum, typical, and maximum value of a quantity.

The minimum, typical value, and/or maximum value may all be left undefined (but at least one
must be defined). This permits the construction of specifications which are unbounded (e.g.
have no minimum value) or which do not have a typical value.

The maximum is allowed to be less than the minimum, and the typical value need not be between
the minimum and maximum.
\tparam T an arithmetic type or a type which behaves like an arithmetic type (has arithmetic
operators like + and - defined).
*/
template<typename T> class Spec {
    typedef boost::optional<T> optional_type;
    //typedef std::optional<T> optional_type; std::optional alternative

    optional_type minimum;
    optional_type typ;
    optional_type maximum;
public:
    typedef T value_type;

    Spec(value_type typ) : minimum(), typ(typ), maximum() {}
    Spec(value_type minimum, value_type maximum) : minimum(minimum), typ(), maximum(maximum) {}
    Spec(value_type minimum, value_type typ, value_type maximum) : minimum(minimum), typ(typ), maximum(maximum) {}
    Spec(nullspec_type minimum, value_type maximum) : minimum(), typ(), maximum(maximum) {}
    Spec(value_type minimum, nullspec_type maximum) : minimum(minimum), typ(), maximum(maximum) {}
    Spec(nullspec_type minimum, value_type typ, value_type maximum) : minimum(minimum), typ(typ), maximum(maximum) {}
    Spec(value_type minimum, value_type typ, nullspec_type maximum) : minimum(minimum), typ(typ), maximum(maximum) {}

    /** \brief A Spec must be constructed with at least one value (minimum, typical or maximum). */
    Spec() = delete;

    constexpr bool has_min() const noexcept { return bool(minimum); }
    constexpr bool has_typical() const noexcept { return bool(typ); }
    constexpr bool has_max() const noexcept { return bool(maximum); }

    constexpr value_type min() const {
        if (minimum) return *minimum;
        else throw bad_spec_access("attempted to access a non-existent minimum spec");
    }

    constexpr value_type typical() const {
        if (typ) return *typ;
        else throw bad_spec_access("attempted to access a non-existent typical spec");
    }

    constexpr value_type max() const {
        if (maximum) return *maximum;
        else throw bad_spec_access("attempted to access a non-existent maximum spec");
    }

    /** \brief Returns the minimum value if it exists, or the supplied value if not.
    \tparam V a type which can be cast to \ref value_type.
    \param[in] default_value the value to return if the Spec does not have a defined minimum.
    */
    template<typename V>
    constexpr value_type min_or(const V& default_value) const {
        return minimum.value_or(default_value);
    }

    /** \brief Returns the typical value if it exists, or the supplied value if not.
    \tparam V a type which can be cast to \ref value_type.
    \param[in] default_value the value to return if the Spec does not have a defined typical value.
    */
    template<typename V>
    constexpr value_type typical_or(const V& default_value) const {
        return typ.value_or(default_value);
    }

    /** \brief Returns the maximum value if it exists, or the supplied value if not.
    \tparam V a type which can be cast to \ref value_type.
    \param[in] default_value the value to return if the Spec does not have a defined maximum.
    */
    template<typename V>
    constexpr value_type max_or(const V& default_value) const {
        return maximum.value_or(default_value);
    }

    /** \brief Returns the minimum value if it exists and is greater than the supplied clamp value,
    otherwise the supplied clamp value.

    Similar to min_or(), except that the return value is guaranteed to be greater than or equal to the
    clamp value. The argument to min_or() may be used to clamp its return value to a specified value if
    the Spec minimum exists, but not if the Spec has a minimum which is less than the argument to min_or().
    */
    template<typename V>
    constexpr value_type clamped_min(const V& clamp_minimum) const {
        // Acquire the minimum value, or use clamp_minimum if the minimum has no value
        // Clamp that value (if minimum has a value it might be less than clamp_minimum)
        return clamp_min(static_cast<value_type>(minimum.value_or(clamp_minimum)),
                         static_cast<value_type>(clamp_minimum));
    }

    /** \brief Returns the minimum value if it exists and is within the range specified by the clamp values.
    If the minimum value exists but is outside the range specified by the clamp values then the clamp value
    closest to the minimum is returned. If the minimum value doesn't exist then the supplied clamp minimum
    is returned.

    Similar to min_or(), except that the return value is guaranteed to be within the range of the clamp
    values. The argument to min_or() may be used to clamp its return value to a specified value if
    the Spec minimum exists, but not if the Spec has a minimum which is less than the argument to min_or().
    */
    template<typename V>
    constexpr value_type clamped_min(const V& clamp_minimum, const V& clamp_maximum) const {
        // Acquire the minimum value, or use clamp_minimum if the minimum has no value
        // Clamp that value (if minimum has a value it might not be within the range of clamp_minimum to clamp_maximum)
        return clamp(static_cast<value_type>(minimum.value_or(clamp_minimum)),
                         static_cast<value_type>(clamp_minimum), static_cast<value_type>(clamp_maximum));
    }

    /** \brief Clamps the typical value if it exists to within the range specified by the supplied
    clamp values.

    Similar to typical_or(), except that the return value is guaranteed to be within the range specified
    by the supplied clamp values. The argument to typical_or() may be used to clamp its return value to a
    specified value if the Spec typical exists, but not to a range of values and not if the Spec has a
    typical which is outside a clamp range.
    */
    template<typename V>
    constexpr value_type clamped_typical(const V& clamp_minimum, const V& clamp_maximum) const {
        if (typ) return clamp(*typ, static_cast<value_type>(clamp_minimum), static_cast<value_type>(clamp_maximum));
        else throw bad_spec_access("cannot clamp a non-existent typical value");
    }

    /** \brief Clamps the typical value if it exists to within the range specified by the supplied
    clamp values, or returns the specified default value.

    Similar to clamped_typical(const V&, const V&), except that a default value is returned in the
    case where clamped_typical(const V&, const V&) throws an exception. Also similar to typical_or(),
    except that the return value is guaranteed to be within the range specified by the supplied clamp
    values if the Spec typical is defined.
    */
    template<typename V>
    constexpr value_type clamped_typical(const V& clamp_minimum, const V& clamp_maximum, const V& default_value) const {
        if (typ) return clamp(*typ, static_cast<value_type>(clamp_minimum), static_cast<value_type>(clamp_maximum));
        else return default_value;
    }

    /** \brief Returns the maximum value if it exists and is less than the supplied clamp value,
    otherwise the supplied clamp value.

    Similar to max_or(), except that the return value is guaranteed to be less than or equal to the
    clamp value. The argument to max_or() may be used to clamp its return value to a specified value if
    the Spec maximum exists, but not if the Spec has a maximum which is greater than the argument to max_or().
    */
    template<typename V>
    constexpr value_type clamped_max(const V& clamp_maximum) const {
        // Acquire the maximum value, or use clamp_maximum if the maximum has no value
        // Clamp that value (if maximum has a value it might be greater than clamp_maximum)
        return clamp_max(static_cast<value_type>(maximum.value_or(clamp_maximum)),
                         static_cast<value_type>(clamp_maximum));
    }

    /** \brief Returns the maximum value if it exists and is within the range specified by the clamp values.
    If the maximum value exists but is outside the range specified by the clamp values then the clamp value
    closest to the maximum is returned. If the maximum value doesn't exist then the supplied clamp maximum
    is returned.

    Similar to max_or(), except that the return value is guaranteed to be within the range of the clamp
    values. The argument to max_or() may be used to clamp its return value to a specified value if
    the Spec maximum exists, but not if the Spec has a maximum which is outside the clamp range.
    */
    template<typename V>
    constexpr value_type clamped_max(const V& clamp_minimum, const V& clamp_maximum) const {
        // Acquire the maximum value, or use clamp_maximum if the maximum has no value
        // Clamp that value (if maximum has a value it might not be within the range of clamp_minimum to clamp_maximum)
        return clamp(static_cast<value_type>(maximum.value_or(clamp_maximum)),
                     static_cast<value_type>(clamp_minimum), static_cast<value_type>(clamp_maximum));
    }

    /** \brief Determines if two Specs are equal.

    Two Specs are considered equal if all the following are true:
    1. Both Specs have equal minimum values or both Specs have an undefined minimum.
    2. Both Specs have equal typical values or both Specs have an undefined typical value.
    3. Both Specs have equal maximum values or both Specs have an undefined maximum.
    \tparam U a value_type which can be cast to type `T`.
    */
    template<typename T, typename U>
    friend inline bool operator==(const Spec<T>& lhs, const Spec<U>& rhs);

    template<typename T, typename U>
    friend inline bool operator!=(const Spec<T>& lhs, const Spec<U>& rhs);

    /** \brief Inserts a Spec into a `std::ostream`.

    Inserts a string of the form '[min, typical, max]', where 'min', 'typical', and 'max' are the minimum,
    typical, and maximum values of the Spec, respectively. If a minimum value is not defined then the
    string "-Inf" is used (to symbolize negative infinity). If a maximum value is not defined then the
    string "Inf" is used (to symbolize positive infinity). If a typical value is not defined then the
    substring 'typical, ' is omitted -- the inserted string has the form '[min, max]'.

    Square brackets are used to denote closed intervals if `spec` has a defined minimum and/or maximum,
    and parentheses are used to denote open intervals if `spec` has an undefined minimum and/or maximum.
    For example, the string for a `spec` with a minimum of 0 and an undefined maximum is '[0, Inf)`.
    \tparam T a type for which operator<<(std::ostream, T) is defined.
    */
    template<typename T>
    friend inline std::ostream& operator<<(std::ostream& os, const Spec<T>& spec);
};

template<typename T, typename U>
inline bool operator==(const Spec<T>& lhs, const Spec<U>& rhs) {
    // Two optionals are equal if:
    // 1. they both contain a value and those values are equal
    // 2. neither contains a value
    return (lhs.minimum == rhs.minimum) && (lhs.typ == rhs.typ) && (lhs.maximum == rhs.maximum);
}

template<typename T, typename U>
inline bool operator!=(const Spec<T>& lhs, const Spec<U>& rhs) { return !(lhs == rhs); }

template<typename T>
inline std::ostream& operator<<(std::ostream& os, const Spec<T>& spec) {
    if (spec.has_min()) os << '[' << spec.min();
    else os << '(' << "-Inf";

    os << ", ";

    if (spec.has_typical()) os << spec.typical() << ", ";

    if (spec.has_max()) os << spec.max() << ']';
    else os << "Inf" << ')';

    return os;
}

/** \brief Clamps a value to within a specified range.

Based on std::clamp() in C++17: http://en.cppreference.com/w/cpp/algorithm/clamp.
\throws std::logic_error if `min > max`
*/
template<typename T>
constexpr const T& clamp(const T& value, const T& min, const T& max) {
    if (min > max) throw std::logic_error("cannot clamp between a min and max value when min > max");

    return value < min ? min : value > max ? max : value;
}

/** \brief Clamps a value to greater than or equal to a specified minimum. */
template<typename T>
constexpr const T& clamp_min(const T& value, const T& min) {
    return value < min ? min : value;
}

/** \brief Clamps a value to less than or equal to a specified maximum. */
template<typename T>
constexpr const T& clamp_max(const T& value, const T& max) {
    return value > max ? max : value;
}

/** \brief Clamps the values held by a Spec to within a specified range.

\return a new `Spec<T>`, `s`, with its minimum, typical, and maximum values clamped to the range specified
by `min` and `max`. If `spec` does not have a defined minimum then `s` has its minimum set to `min`.
If `spec` does not have a defined maximum then `s` has its maximum set to `max`. If `spec` does not
have a defined typical then `s` does not have a defined typical, either. If `spec` has a defined typical
then the defined typical for `s` is either equal to that of `spec` (if the typical is within the clamp
limits) or set to the closest clamp value (e.g. if `spec.typical() > max` then `s.typical() = max`).
\throws std::logic_error if `min > max`
*/
template<typename T>
constexpr Spec<T> clamp(const Spec<T>& spec, const T& min, const T& max) {
    if (min > max) throw std::logic_error("cannot clamp between a min and max value when min > max");

    auto clamped_min = spec.clamped_min(min);
    auto clamped_max = spec.clamped_max(max);

    if (spec.has_typical()) {
        auto clamped_typical = clamp(spec.typical(), min, max);

        return Spec<T>(clamped_min, clamped_typical, clamped_max);
    } else return Spec<T>(clamped_min, clamped_max);
}

/** \brief Determines if a value is between the limits of a Spec.

\return `true` if `value` is greater than or equal to the Spec argument's minimum and less than or
equal to the Spec argument's maximum, `false` otherwise. If the Spec argument does not have a minimum
then `value` is considered greater than the Spec argument's minimum. If the Spec argument does not
have a maximum then `value` is considered less than the Spec argument's maximum. For example, if `spec`
has a minimum but no maximum then `true` is returned if `value >= spec.min()`, `false` otherwise. If
`spec` has neither a minimum nor maximum then `true` is returned for any `value`.
*/
template<typename V, typename T>
inline bool pass(const V& value, const Spec<T>& spec) {
    if (spec.has_min() && value < spec.min()) return false;
    if (spec.has_max() && value > spec.max()) return false;

    return true;
}

template<typename V, typename T>
inline bool fail(const V& value, const Spec<T>& spec) {
    return !pass(value, spec);
}

/** \brief Calculates Spec limits based on an expected value and a % tolerance.

\return a Spec with `value_type` set to the same type as `value`, a minimum equal to
`(1 - tolerance / 100) * value`, a typical equal to `value`, and a maximum equal to
`(1 + tolerance / 100) * value`. For example, for a 1% tolerance the minimum is equal to 99% of `value`
and the maximum is equal to 101% of `value`.
*/
template<typename V, typename T>
inline Spec<V> tolerance_spec(const V& value, const T& tolerance) {
    return Spec<V>((1 - tolerance / 100.0) * value, value, (1 + tolerance / 100.0) * value);
}

/** \brief Calculates Spec limits based on an expected value and a % tolerance and a clamping range.

\return a Spec with `value_type` set to the same type as `value`, a typical equal to `value`, a
minimum set to `(1 - tolerance / 100) * value` or `clamp_min` (whichever is greater), and a maximum
set to `(1 + tolerance / 100) * value` or `clamp_max` (whichever is less). For example, for a 1%
tolerance the minimum is equal to 99% of `value` and the maximum is equal to 101% of `value` unless
the minimum and/or maximum exceed the clamp range (in which case the minimum and/or maximum are set
to the clamp values).
*/
template<typename V, typename T>
inline Spec<V> tolerance_spec(const V& value, const T& tolerance, const V& clamp_minimum, const V& clamp_maximum) {
    // Min/max based on tolerance calculation
    V tol_min = (1 - tolerance / 100.0) * value;
    V tol_max = (1 + tolerance / 100.0) * value;

    // Clamp the min/max based on the tolerance calculation to the minimum/maximum
    clamp(tol_min, clamp_minimum, clamp_maximum);
    clamp(tol_max, clamp_minimum, clamp_maximum);

    return Spec<V>(tol_min, value, tol_max);
}

#endif

Вот демо программа Spec и сопутствующие услуги:

#include <iostream>
#include <chrono>
#include <stdexcept>
#include <vector>
#include <fstream>

#include "Spec.h"

using namespace std::chrono_literals;

template<typename T>
void print_unchecked_accesses(std::ostream& os, Spec<T> spec) {
    try {
        auto min = spec.min();
    } catch (const bad_spec_access& err) {
        os << err.what() << '\n';
    }

    try {
        auto typ = spec.typical();
    } catch (const bad_spec_access& err) {
        os << err.what() << '\n';
    }

    try {
        auto max = spec.max();
    } catch (const bad_spec_access& err) {
        os << err.what() << '\n';
    }

    os << '\n';
}

template<typename T>
void print_checked_accesses(std::ostream& os, Spec<T> spec) {
    if (spec.has_min()) auto min = spec.min();
    else os << "non-existent min\n";

    if (spec.has_typical()) auto typ = spec.typical();
    else os << "non-existent typical\n";

    if (spec.has_max()) auto max = spec.max();
    else os << "non-existent max\n";

    os << '\n';
}

template<typename T, typename V>
void print_access_or(std::ostream& os, Spec<T> spec, V default_value) {
    os << "Accesses with default value " << default_value << " provided: ";
    os << spec.min_or(default_value) << ", ";
    os << spec.typical_or(default_value) << ", ";
    os << spec.max_or(default_value) << "\n\n";
}

template<typename T, typename V>
void test_clamps(std::ostream& os, Spec<T> spec, V clamp_min, V clamp_max) {
    os << spec << " limits clamped to " << clamp(spec, clamp_min, clamp_max)
        << " with clamps " << clamp_min << " and " << clamp_max << '\n';

    V default_value = 0;
    os << "Clamping typical with default value " << default_value << ": "
        << spec.clamped_typical(clamp_min, clamp_max, default_value) << '\n';

    os << "Clamping typical without a default value: ";
    try {
        os << spec.clamped_typical(clamp_min, clamp_max);
    } catch (const bad_spec_access& err) {
        os << err.what();
    }
    os << '\n';
}

int main() {
    std::ofstream ofs("demo.txt");

    double clamp_min = 0;
    double clamp_max = 15;

    std::vector<Spec<double> > specs = {
        Spec<double>(0, 5), // double-sided
        Spec<double>(-5, nullspec), // min only
        Spec<double>(nullspec, 298), // max only
        Spec<double>(90), // typical only
        Spec<double>(-79, 235, 89235), // min, typical, and max
        Spec<double>(tolerance_spec<double, int>(5, 10)),
        Spec<double>(tolerance_spec<double, int>(15, 10, clamp_min, clamp_max))
    };

    for (auto spec : specs) {
        ofs << "Spec: " << spec << '\n';
        ofs << "Exceptions caught due to unchecked accesses:\n";
        print_unchecked_accesses(ofs, spec);

        ofs << "Non-existent values determined from checked accesses:\n";
        print_checked_accesses(ofs, spec);

        print_access_or(ofs, spec, -1);

        test_clamps(ofs, spec, clamp_min, clamp_max);

        ofs << "--------------------------------------------------------------------------------\n";
    }

    ofs << "Testing equality:\n";
    using TimeSpec = Spec<std::chrono::microseconds>;
    TimeSpec::value_type max = 5us;

    TimeSpec t1(-15us, 0us, max);
    TimeSpec t2 = t1;
    TimeSpec t3(-10us, 0us, max); // unequal min
    TimeSpec t4(-15us, 1us, max); // unequal typical
    TimeSpec t5(-15us, 0us, 6us); // unequal max

    std::vector<TimeSpec> timespecs{t1, t2, t3, t4, t5};

    ofs << std::boolalpha;

    for (std::size_t t = 1; t < timespecs.size(); t++) {
        ofs << "t1 == t" << t + 1 << "?: " << (t1 == timespecs[t]) << '\n';
    }

    ofs << '\n';

    Spec<double> spec(-15, 0, 5);

    ofs << "Testing pass/fail with Spec " << spec << "\n";
    for (auto value : {-16, -15, 0, 5, 10}) {
        ofs << value << " passes?: " << pass(value, spec) << '\n';
    }

    return 0;
}

Демо-выходы программы:

Spec: [0, 5]
Exceptions caught due to unchecked accesses:
attempted to access a non-existent typical spec

Non-existent values determined from checked accesses:
non-existent typical

Accesses with default value -1 provided: 0, -1, 5

[0, 5] limits clamped to [0, 5] with clamps 0 and 15
Clamping typical with default value 0: 0
Clamping typical without a default value: cannot clamp a non-existent typical value
--------------------------------------------------------------------------------
Spec: [-5, Inf)
Exceptions caught due to unchecked accesses:
attempted to access a non-existent typical spec
attempted to access a non-existent maximum spec

Non-existent values determined from checked accesses:
non-existent typical
non-existent max

Accesses with default value -1 provided: -5, -1, -1

[-5, Inf) limits clamped to [0, 15] with clamps 0 and 15
Clamping typical with default value 0: 0
Clamping typical without a default value: cannot clamp a non-existent typical value
--------------------------------------------------------------------------------
Spec: (-Inf, 298]
Exceptions caught due to unchecked accesses:
attempted to access a non-existent minimum spec
attempted to access a non-existent typical spec

Non-existent values determined from checked accesses:
non-existent min
non-existent typical

Accesses with default value -1 provided: -1, -1, 298

(-Inf, 298] limits clamped to [0, 15] with clamps 0 and 15
Clamping typical with default value 0: 0
Clamping typical without a default value: cannot clamp a non-existent typical value
--------------------------------------------------------------------------------
Spec: (-Inf, 90, Inf)
Exceptions caught due to unchecked accesses:
attempted to access a non-existent minimum spec
attempted to access a non-existent maximum spec

Non-existent values determined from checked accesses:
non-existent min
non-existent max

Accesses with default value -1 provided: -1, 90, -1

(-Inf, 90, Inf) limits clamped to [0, 15, 15] with clamps 0 and 15
Clamping typical with default value 0: 15
Clamping typical without a default value: 15
--------------------------------------------------------------------------------
Spec: [-79, 235, 89235]
Exceptions caught due to unchecked accesses:

Non-existent values determined from checked accesses:

Accesses with default value -1 provided: -79, 235, 89235

[-79, 235, 89235] limits clamped to [0, 15, 15] with clamps 0 and 15
Clamping typical with default value 0: 15
Clamping typical without a default value: 15
--------------------------------------------------------------------------------
Spec: [4.5, 5, 5.5]
Exceptions caught due to unchecked accesses:

Non-existent values determined from checked accesses:

Accesses with default value -1 provided: 4.5, 5, 5.5

[4.5, 5, 5.5] limits clamped to [4.5, 5, 5.5] with clamps 0 and 15
Clamping typical with default value 0: 5
Clamping typical without a default value: 5
--------------------------------------------------------------------------------
Spec: [13.5, 15, 16.5]
Exceptions caught due to unchecked accesses:

Non-existent values determined from checked accesses:

Accesses with default value -1 provided: 13.5, 15, 16.5

[13.5, 15, 16.5] limits clamped to [13.5, 15, 15] with clamps 0 and 15
Clamping typical with default value 0: 15
Clamping typical without a default value: 15
--------------------------------------------------------------------------------
Testing equality:
t1 == t2?: true
t1 == t3?: false
t1 == t4?: false
t1 == t5?: false

Testing pass/fail with Spec [-15, 0, 5]
-16 passes?: false
-15 passes?: true
0 passes?: true
5 passes?: true
10 passes?: false

Я ищу предложения по дизайну, имен и т. д. Кроме того, несколько специфических проблем, которые я ценю рецензентов комментировать:

  1. Я все еще учусь, как использовать всю мощь языка C++11 и C++14, так как я был с помощью до C++11 компилятор долгое время, так есть ли в C++11 или C++14 я забыл использовать что бы улучшить код?
  2. Я не очень комфортно с Move семантикой еще. Есть некоторые оптимизации, которые я упустил из-за этого?
  3. Я предоставил никаких функций для изменения внутренних данных Spec класса (она неизменна). Это упрощает конструкцию классе немного (например, у меня нет, чтобы проверить, что хотя бы один из минимальных/типовое/максимальное значения определяются при изменении экземпляра-мне нужно просто удалить конструктор по умолчанию). Однако, в работе это иногда было неудобно не сможет изменить Spec экземпляр после того, как он построен (например, на приобретение Spec экземпляр с ограничениями, взятой из другого экземпляра, но зажимается-у меня есть, чтобы скопировать его вместо). Было мое решение класс неизменяемых хороший, или не ограничивает свою полезность?
  4. Было бы неплохо, чтобы обнаружить, если код компилируется с помощью компилятора, поддерживающего std::optional и использовать std::optional если это так, или пытаться падать обратно на boost::optional если нет. Есть ли способ сделать это без особых трудностей?


178
7
задан 7 февраля 2018 в 05:02 Источник Поделиться
Комментарии
2 ответа

Именование:

Будьте последовательны, и желательно избегать сокращений. Члены minimum, typ и maximumно тут еще has_min(), has_typical() и has_max()и т. д. Это может быть аккуратнее, чтобы просто писать все это каждый раз.

Конструкторы:

Э... это много строителей. ИМХО, было бы гораздо аккуратнее сделать optional_type typedef для общественности, и только один конструктор принимает необязательный тип для всех трех параметров:

Spec(optional_type const& minimum, optional_type const& typical, optional_type const& maximum):
minimum(minimum), typ(typical), maximum(maximum) { }

Можно еще пройти value_type или nullspec напрямую (по крайней мере с std::optionalЯ не проверял boost::optional), но теперь он мгновенно становится очевидным, что происходит, потому что вы должны определить все три параметра каждый раз:

Spec<double>(0.0, nullspec, 5.0);

Не нужно явно удалить конструктор по умолчанию, поскольку вы определили по умолчанию конструкторы.

Другие:


  • Там нет необходимости для статических отданных результаты minimum.value_or() в clamped_min()потому что std::optional::value_or() будет возвращает value_type уже. Же максимальная версия.

  • Вы могли бы проверить для отрицательной толерантности в tolerance_spec() (хотя врачи скажут, что максимум может быть ниже, чем минимальная, так что может и нет...).

  • (Баг?:) В tolerance_specвам не кажется, что используя результат clamp операции!

Ваши Вопросы:


  1. Нет в куче членов, так что нет ничего, что бы быстрее двигаться, чем копия. Компилятор в любом случае создаст конструктор перемещения для вас.

  2. Если set_foo() функции-члены будут полезны, нет никакой причины не предоставить их. Если экземпляр не должны быть изменены, что const и const& для. Похож на конструктор, они могут принимать optional_typeвместо value_type.

2
ответ дан 7 февраля 2018 в 07:02 Источник Поделиться

Конструкторы

Что касается длинного списка разных конструкторов, я думаю, ты написал это с кем-то вручную писать конструктор вызывает. Т. е. вы пытаетесь свести к минимуму "почерк" усилий, если предположить, что большинство из этих значений будут выходить некоторые описания файла, смотрим, как это можно читать, а какие из них больше всего используется. Думают ли другие из них стоит оставить. Что-то вроде

Spec spec1{none, 12, none};  
Spec spec2{0.5, 12, none};
...

также изначально более читабельным, так как вам не нужно проследить в каком виде конструктор был вызван. Также МСИО, по крайней мере для boost::any<T> т в виде неявной конвертации boost::any<T>. Т. е. вы можете позвонить

 void func(boost::any<double> x); 

через func(12.0) это может также помочь сокращению количества конструкторов вниз.

Mutablity

Что касается переменчивости, вы могли бы рассмотреть вопрос о внесении свой класс изменчива, но с использованием const переменная для указания неизменяемого значения. Для этого я предлагаю изменить название min\typical\max методы доступа к min_value, typical_valueи т. д. это позволяет вам ввести две других методов доступа

 boost::any<V>& min() {return minimum};
const boost::any<V>& min() const {return minimum};

В зависимости от стиля, который вам нравится, это может позволить вам избавиться от has_value функции, как вы могли бы сейчас сделать

 if (spec.min()) { ... }

То же хотел бы пойти на функции по умолчанию ...

 spec.min().value_or(default);

да это представляет собой базовый тип, но иногда (иногда) это не обязательно плохо, epsecially, когда вы окажетесь писать много функций, которые просто обернуть интерфейс внутренний класс. Все зависит от целей.

Мысли ...

Я уставилась на свой интерфейс на некоторое время, не уверен, если я пришел к хорошему выводу, целую серию зажима называть просто кажется очень сложным со стороны, но я действительно не мог найти лучшую структуру. Мы видим только интерфейс класса не требования, которые создали этот интерфейс. Может быть, идеи от переменчивости может измениться, что ...

Обнаружение

Что касается поддержки компилятора, если вы используете cmake вы можете проверить существование файлов, в противном случае вам придется просто пойти и проверить для конкретного компилятора версии, я думаю, что последние выпуски большой 3 лязгом, на GCC и MSVC все std:any реализовано.

Другие

Выглядит ostream функтор не надо быть friend это хорошо.

Основные руководящие принципы предполагают, предпочитая using за typedef если ты просто возвращаешься в c++ это хорошая привычка, чтобы забрать. using лучше в общении с шаблонами, а также.

1
ответ дан 10 февраля 2018 в 10:02 Источник Поделиться