Значение по умолчанию для переменной-члена класса С++: ссылка на нее позже

У меня есть класс C с переменными-членами, так что для каждой, если она не установлена, должно быть установлено значение по умолчанию. У меня есть много переменных-членов в C и различные конструкторы, которые устанавливают одни переменные и не устанавливают другие и т. д., поэтому, чтобы быть уверенным, что переменные-члены, которые должны иметь значения по умолчанию, установлены со своими значениями по умолчанию, я полагаюсь на void Init() функция-член: в Init я устанавливаю для всех переменных-членов, имеющих значения по умолчанию, их значения по умолчанию, и я вызываю Init в своих конструкторах и т. д.

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

Мой вопрос: как лучше всего реализовать концепцию «значение по умолчанию для переменной-члена»? Через константу, определенную в заголовке с объявлением C ? В качестве переменной-члена const? Как static const переменная-член?

Примечание: мне разрешено c++ <= 2003.


person Olorin    schedule 30.10.2016    source источник
comment
Поместите значения в список инициализаторов конструктора.   -  person    schedule 30.10.2016
comment
Мне разрешено c++ <= 2003, я ответил на вопрос   -  person Olorin    schedule 30.10.2016


Ответы (3)


Если у вас есть «различные конструкторы», вам нужно добавить инициализацию переменных-членов, которые не инициализированы в коде конструкторов в списках инициализаторов.

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

Современный C++ позволяет вам наследовать конструктор, когда один конструктор может вызвать (базовый) конструктор, например здесь.

person harper    schedule 30.10.2016
comment
Как это ответ на мой вопрос? - person Olorin; 30.10.2016

Вот идея, которая работает в С++ 03:

template <class T> struct Default_value
{
private:
  T value_;
  T default_value_;

public:
  Default_value(const T& default_value)
    : value_(default_value), default_value_(default_value)
  {}

  const T& get() const { return value_; }
  T& get() { return value_; }

  const T& get_default() const { return default_value_; }
  bool is_default() const { return value_ == default_value_; }
};

struct X_init {
  Default_value<int> a_, b_;
  Default_value<std::string> str_;

  X_init() : a_(24), b_(42), str_("This is sparta") {}

  X_init& set_a(int a) { a_.get() = a; return *this; }
  X_init& set_b(int b) { b_.get() = b; return *this; }
  X_init& set_str(const std::string& str) { str_.get() = str; return *this; } 
};

struct X {
  X_init values_;

  X() : values_() {}
  X(const X_init& values) : values_(values) {}

  //... X implementation
};
int main()
{
  X x = X_init().set_a(32).set_str("nope");

  cout << std::boolalpha;
  cout << "a: " << x.values_.a_.get() << " " << x.values_.a_.is_default() << endl;
  cout << "b: " << x.values_.b_.get() << " " << x.values_.b_.is_default() << endl;
  cout << "str: " << x.values_.str_.get() << " " << x.values_.str_.is_default() << endl;
}
a: 32 false
b: 42 true
str: nope false

Вы должны сделать больше работы, но он делает именно то, что вы хотите.

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

Идея проста. У нас есть шаблонный класс Default_value. Это позволяет нам явно установить желаемое значение по умолчанию и отслеживать, изменилось ли значение по сравнению со значением по умолчанию или нет.

Затем у нас есть X_init класс, специально разработанный для инициализации членов X. Преимущество заключается в том, что вы можете связать сеттеры и, таким образом, вы можете явно установить некоторые члены, оставив остальные по умолчанию. Это известно как идиома именованного параметра.

Недостатком этого метода является то, что все элементы данных X объединены в класс X_init. Если вам это не нравится, вы можете включить логику X_init в X:

struct X {
  Default_value<int> a_, b_;
  Default_value<std::string> str_;


  X() : a_(24), b_(42), str_("This is sparta") {}

  X& set_a(int a) { a_.get() = a; return *this; }
  X& set_b(int b) { b_.get() = b; return *this; }
  X& set_str(const std::string& str) { str_.get() = str; return *this; } 
};

int main()
{
  X x = X().set_a(32).set_str("nope");

  cout << std::boolalpha;
  cout << "a: " << x.a_.get() << " " << x.a_.is_default() << endl;
  cout << "b: " << x.b_.get() << " " << x.b_.is_default() << endl;
  cout << "str: " << x.str_.get() << " " << x.str_.is_default() << endl;
}
person bolov    schedule 31.10.2016

Один из способов сохранить ваши значения по умолчанию — это constexpr static экземпляр специально созданного LiteralType, что позволит вам свериться с его членами. Любые переменные-члены, которые могут быть созданы во время компиляции, достаточно легко хранить, но вам потребуется создать constexpr конструируемую оболочку для типов только во время выполнения, таких как std::string. Затем, когда компилятору будет предложено оптимизировать код, он сможет полностью удалить экземпляр «значений по умолчанию», если вы не возьмете его адрес или не сделаете что-то в этом роде.

Если, например, у вас есть класс CQQC (имя выбрано для краткости и уникальности), в котором хранятся три int и std::string со значениями по умолчанию 0, 42, 359 и "Hey, y'all!", вы можете сделать что-то вроде этого:

// Forward declaration for our "default values" class.
class CQQC;

namespace detail {
    // String wrapper, convertible to std::string.
    class literal_string {
        const char* const str;
        size_t sz; // Currently not needed, but useful if additional functionality is desired.

        static constexpr size_t length(const char* const str_) {
            return *str_ ? 1 + length(str_ + 1) : 0;
        }

      public:
        template<size_t N>
        constexpr literal_string(const char (&str_)[N])
          : str(str_), sz(N - 1) { }

        constexpr literal_string(const char* const str_)
          : str(str_), sz(length(str_)) { }

        operator std::string() const {
            return std::string(str);
        }
    };

    // Generic "default values" type, specialise for each class.
    template<typename>
    struct Defaults_;

    // Defaults for CQQC.
    template<>
    struct Defaults_<::CQQC> {
        int i, j, k;
        literal_string str;

/*
        template<size_t N = 12>
        constexpr Defaults_(int i_ = 0,
                            int j_ = 42,
                            int k_ = 359,
                            const char (&str_)[N] = "Hey, y'all!")
          : i(i_), j(j_), k(k_), str(str_) { }
*/

        constexpr Defaults_(int i_ = 0,
                            int j_ = 42,
                            int k_ = 359,
                            const char* const str_ = "Hey, y'all!")
          : i(i_), j(j_), k(k_), str(str_) { }
    };
} // namespace detail

// Boilerplate macro.
#define MAKE_DEFAULTS(Class) \
    constexpr static ::detail::Defaults_<Class> Defaults = ::detail::Defaults_<Class>()

Затем CQQC создает статический экземпляр времени компиляции своего класса "значения по умолчанию" (или выдает ошибку, если класс не является LiteralType). Затем он будет обращаться к этому экземпляру всякий раз, когда ему нужно проверить значения по умолчанию.

class CQQC {
    static_assert(std::is_literal_type<detail::Defaults_<CQQC>>::value,
                  "Default value holder isn't a literal type.");

    MAKE_DEFAULTS(CQQC);
    // Expands to:
    // constexpr static ::detail::Defaults_<CQQC> Defaults = ::detail::Defaults_<CQQC>();

    int i, j, k;
    std::string str;

  public:
    // Allows the user to specify that they want the default value.
    enum Flags { DEFAULT };

    // Initialise to defaults, unless otherwise specified.
    CQQC(int i_ = Defaults.i,
         int j_ = Defaults.j,
         int k_ = Defaults.k,
         std::string str_ = Defaults.str)
      : i(i_), j(j_), k(k_), str(str_) {}

    bool isDefault() {
        return (i   == Defaults.i &&
                j   == Defaults.j &&
                k   == Defaults.k &&
                str == Defaults.str.operator std::string());
    }

    void set_i(int i_) { i = i_; }

    // Set to default.
    void set_i(Flags f_) {
        if (f_ == Flags::DEFAULT) { i = Defaults.i; }
    }

    // And so on...
};
constexpr detail::Defaults_<CQQC> CQQC::Defaults;

Посмотрите это в действии здесь.


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

Используя приведенный выше пример:

// main.cpp
#include <iostream>
#include "cqqc.h"

int main() {
    CQQC c1(4);
    CQQC c2;

    if (c1.isDefault()) {
        std::cout << "c1 has default values.\n";
    } else {
        std::cout << "c1 is weird.\n";
    }

    if (c2.isDefault()) {
        std::cout << "c2 has default values.\n";
    } else {
        std::cout << "c2 is weird.\n";
    }

    c1.set_i(CQQC::DEFAULT);
    if (c1.isDefault()) {
        std::cout << "c1 now has default values.\n";
    } else {
        std::cout << "c1 is still weird.\n";
    }
}

// -----

// defs.h
#ifndef DEFS_H
#define DEFS_H

#include <string>

namespace detail {
    // String wrapper, convertible to std::string.
    class literal_string {
        const char* const str;
        size_t sz; // Currently not needed, but useful if additional functionality is desired.

        static constexpr size_t length(const char* const str_) {
            return *str_ ? 1 + length(str_ + 1) : 0;
        }

      public:
        template<size_t N>
        constexpr literal_string(const char (&str_)[N])
          : str(str_), sz(N - 1) { }

        constexpr literal_string(const char* const str_)
          : str(str_), sz(length(str_)) { }

        operator std::string() const {
            return std::string(str);
        }
    };

    // Generic "default values" type, specialise for each class.
    template<typename>
    struct Defaults_;
} // namespace detail

// Boilerplate macro.
#define MAKE_DEFAULTS(Class) \
    constexpr static ::detail::Defaults_<Class> Defaults = ::detail::Defaults_<Class>()

#endif // DEFS_H

// -----

// cqqc.h
#ifndef CQQC_H
#define CQQC_H

#include <string>
#include <type_traits>
#include "defs.h"

// Forward declaration for our "default values" class.
class CQQC;

namespace detail {
    // Defaults for CQQC.
    template<>
    struct Defaults_<::CQQC> {
        int i, j, k;
        literal_string str;

/*
        template<size_t N = 12>
        constexpr Defaults_(int i_ = 0,
                            int j_ = 42,
                            int k_ = 359,
                            const char (&str_)[N] = "Hey, y'all!")
          : i(i_), j(j_), k(k_), str(str_) { }
*/

        constexpr Defaults_(int i_ = 0,
                            int j_ = 42,
                            int k_ = 359,
                            const char* const str_ = "Hey, y'all!")
          : i(i_), j(j_), k(k_), str(str_) { }
    };
} // namespace detail

class CQQC {
    static_assert(std::is_literal_type<detail::Defaults_<CQQC>>::value,
                  "Default value holder isn't a literal type.");

    MAKE_DEFAULTS(CQQC);
    // Expands to:
    // constexpr static ::detail::Defaults_<CQQC> Defaults = ::detail::Defaults_<CQQC>();

    int i, j, k;
    std::string str;

  public:
    // Allows the user to specify that they want the default value.
    enum Flags { DEFAULT };

    // Initialise to defaults, unless otherwise specified.
    CQQC(int i_ = Defaults.i,
         int j_ = Defaults.j,
         int k_ = Defaults.k,
         std::string str_ = Defaults.str);

    bool isDefault();

    void set_i(int i_);
    void set_i(Flags f_);

    // And so on...
};

#endif // CQQC_H

// -----

// cqqc.cpp
#include "defs.h"
#include "cqqc.h"

// Initialise to defaults, unless otherwise specified.
CQQC::CQQC(int i_           /* = Defaults.i */,
           int j_           /* = Defaults.j */,
           int k_           /* = Defaults.k */,
           std::string str_ /* = Defaults.str */)
  : i(i_), j(j_), k(k_), str(str_) {}

bool CQQC::isDefault() {
    return (i   == Defaults.i &&
            j   == Defaults.j &&
            k   == Defaults.k &&
            str == Defaults.str.operator std::string());
}

void CQQC::set_i(int i_) { i = i_; }

// Set to default.
void CQQC::set_i(CQQC::Flags f_) {
    if (f_ == Flags::DEFAULT) { i = Defaults.i; }
}

constexpr detail::Defaults_<CQQC> CQQC::Defaults;

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


Сделав это, мы теперь можем сделать этот код обратно совместимым с C++03 без каких-либо серьезных модификаций за счет использования макросов для условного включения <type_traits> и static_assert и условного переключения между constexpr и const в зависимости от значения __cplusplus . Обратите внимание, что даже если это будет сделано, любой результирующий код C++03 может быть не таким эффективным, как эквивалент C++11, поскольку компилятор может не оптимизировать const переменных из готового продукта, а оптимизировать constexpr из них.

Для этого нам нужно определить несколько вспомогательных макросов constexpr вместо прямого использования ключевого слова и изменить шаблонный макрос для C++03 или более ранней версии. (Поскольку последний в любом случае нужно изменить, нет необходимости использовать в нем вспомогательный макрос.):

// constexpr helpers.
#if       __cplusplus >= 201103L
    #define CONSTEXPR_FUNC constexpr
    #define CONSTEXPR_VAR  constexpr
#else  // __cplusplus >= 201103L
    #define CONSTEXPR_FUNC
    #define CONSTEXPR_VAR  const
#endif // __cplusplus >= 201103L

// Boilerplate macro.
#if       __cplusplus >= 201103L
    #define MAKE_DEFAULTS(Class) \
        constexpr static ::detail::Defaults_<Class> Defaults = ::detail::Defaults_<Class>()
#else  // __cplusplus >= 201103L
    #define MAKE_DEFAULTS(Class) \
        const static ::detail::Defaults_<Class> Defaults;
#endif // __cplusplus >= 201103L

Затем нам просто нужно обернуть <type_traits> и static_assert() в блоки #if __cplusplus >= 201103L ... #endif, изменить Flags::DEFAULTS в CQQC::set_i(Flags), потому что перечисления не вводят свою собственную область видимости до C++11 (я просто изменил ее на CQQC::DEFAULT, потому что хотел сохранить ее). область, чтобы уточнить, что это не макрос), и позаботьтесь об одной или двух крошечных проблемах синтаксиса (например, <::CQQC> допустимо в C++ 11, но не в C++ 03, что исправляется добавлением пробела), и вуаля :

// main.cpp
#include <iostream>
#include "cqqc.h"

int main() {
    CQQC c1(4);
    CQQC c2;

    if (c1.isDefault()) {
        std::cout << "c1 has default values.\n";
    } else {
        std::cout << "c1 is weird.\n";
    }

    if (c2.isDefault()) {
        std::cout << "c2 has default values.\n";
    } else {
        std::cout << "c2 is weird.\n";
    }

    c1.set_i(CQQC::DEFAULT);
    if (c1.isDefault()) {
        std::cout << "c1 now has default values.\n";
    } else {
        std::cout << "c1 is still weird.\n";
    }
}

// -----

// defs.h
#ifndef DEFS_H
#define DEFS_H

#include <string>

// constexpr helpers.
#if       __cplusplus >= 201103L
    #define CONSTEXPR_FUNC constexpr
    #define CONSTEXPR_VAR  constexpr
#else  // __cplusplus >= 201103L
    #define CONSTEXPR_FUNC
    #define CONSTEXPR_VAR  const
#endif // __cplusplus >= 201103L

namespace detail {
    // String wrapper, convertible to std::string.
    class literal_string {
        const char* const str;
        size_t sz; // Currently not needed, but useful if additional functionality is desired.

        static CONSTEXPR_FUNC size_t length(const char* const str_) {
            return *str_ ? 1 + length(str_ + 1) : 0;
        }

      public:
        template<size_t N>
        CONSTEXPR_FUNC literal_string(const char (&str_)[N])
          : str(str_), sz(N - 1) { }

        CONSTEXPR_FUNC literal_string(const char* const str_)
          : str(str_), sz(length(str_)) { }

        operator std::string() const {
            return std::string(str);
        }
    };

    // Generic "default values" type, specialise for each class.
    template<typename>
    struct Defaults_;
} // namespace detail

// Boilerplate macro.
#if       __cplusplus >= 201103L
    #define MAKE_DEFAULTS(Class) \
        constexpr static ::detail::Defaults_<Class> Defaults = ::detail::Defaults_<Class>()
#else  // __cplusplus >= 201103L
    #define MAKE_DEFAULTS(Class) \
        const static ::detail::Defaults_<Class> Defaults;
#endif // __cplusplus >= 201103L

#endif // DEFS_H

// -----

// cqqc.h
#ifndef CQQC_H
#define CQQC_H

#include <string>

#if       __cplusplus >= 201103L
    #include <type_traits>
#endif // __cplusplus >= 201103L


#include "defs.h"

// Forward declaration for our "default values" class.
class CQQC;

namespace detail {
    // Defaults for CQQC.
    template<>
    struct Defaults_< ::CQQC> {
        int i, j, k;
        literal_string str;

/*
        // This constructor won't work with C++03, due to the template parameter's default
        //  value.
        template<size_t N = 12>
        CONSTEXPR_FUNC Defaults_(int i_ = 0,
                                 int j_ = 42,
                                 int k_ = 359,
                                 const char (&str_)[N] = "Hey, y'all!")
          : i(i_), j(j_), k(k_), str(str_) { }
*/

        CONSTEXPR_FUNC Defaults_(int i_ = 0,
                                 int j_ = 42,
                                 int k_ = 359,
                                 const char* const str_ = "Hey, y'all!")
          : i(i_), j(j_), k(k_), str(str_) { }
    };
} // namespace detail

class CQQC {
#if       __cplusplus >= 201103L
    static_assert(std::is_literal_type<detail::Defaults_<CQQC>>::value,
                  "Default value holder isn't a literal type.");
#endif // __cplusplus >= 201103L

    MAKE_DEFAULTS(CQQC);
    // C++11: Expands to:
    // constexpr static ::detail::Defaults_<CQQC> Defaults = ::detail::Defaults_<CQQC>();
    // C++03: Expands to:
    // const static ::detail::Defaults_<CQQC> Defaults;

    int i, j, k;
    std::string str;

  public:
    // Allows the user to specify that they want the default value.
    enum Flags { DEFAULT };

    // Initialise to defaults, unless otherwise specified.
    CQQC(int i_ = Defaults.i,
         int j_ = Defaults.j,
         int k_ = Defaults.k,
         std::string str_ = Defaults.str);

    bool isDefault();

    void set_i(int i_);
    void set_i(Flags f_);

    // And so on...
};

#endif // CQQC_H

// -----

// cqqc.cpp
#include "defs.h"
#include "cqqc.h"

// Initialise to defaults, unless otherwise specified.
CQQC::CQQC(int i_           /* = Defaults.i */,
           int j_           /* = Defaults.j */,
           int k_           /* = Defaults.k */,
           std::string str_ /* = Defaults.str */)
  : i(i_), j(j_), k(k_), str(str_) {}

bool CQQC::isDefault() {
    return (i   == Defaults.i &&
            j   == Defaults.j &&
            k   == Defaults.k &&
            str == Defaults.str.operator std::string());
}

void CQQC::set_i(int i_) { i = i_; }

// Set to default.
void CQQC::set_i(CQQC::Flags f_) {
    if (f_ == CQQC::DEFAULT) { i = Defaults.i; }
}

CONSTEXPR_VAR detail::Defaults_<CQQC> CQQC::Defaults;

[Протестировано с GCC 5.3.1 20151207, и C++03 все еще имеет символ для Defaults в объектных файлах, созданных с помощью -O3. Не могу сравнить с MSVC прямо сейчас, у меня не установлена ​​2015 в этой системе, и, поскольку я не знаю, где онлайн-компиляторы MSVC хранят временные объектные файлы, я не могу dumpbin /symbols их онлайн.]

literal_string::length() используется из этого вопроса, потому что это было быстрее, чем писать свой собственный.

person Justin Time - Reinstate Monica    schedule 30.10.2016
comment
Действительно очень мило, но, как я уже писал в своем вопросе, мне разрешено c++ <= c++03, а constexpr это c++ >= c++11... - person Olorin; 31.10.2016
comment
О, я пропустил это. Подождите, я посмотрю, работает ли он с обычным const, и немного перепишу, если нужно. - person Justin Time - Reinstate Monica; 31.10.2016
comment
@ user10000100_u ... И 1) Добавлен конструктор в literal_string, который принимает const char* const, и 2) Использована небольшая магия препроцессора, чтобы сделать его совместимым с С++ 03. (Обратите внимание, что это может быть менее эффективно в C++03, в зависимости от того, может ли компилятор оптимизировать static const переменных-членов.) - person Justin Time - Reinstate Monica; 01.11.2016
comment
Я внимательно изучу ваш код (спасибо еще раз) и вернусь к вам! - person Olorin; 02.11.2016