Один из способов сохранить ваши значения по умолчанию — это 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
c++ <= 2003
, я ответил на вопрос - person Olorin   schedule 30.10.2016