存储默认值的一种方法是作为constexpr static
特制的实例LiteralType
,允许您检查其成员。任何可以在编译时构造的成员变量都很容易存储,但是您需要为constexpr
仅运行时的类型(例如std::string
. 然后,当告诉编译器优化代码时,它应该能够完全删除“默认值”实例,除非您获取它的地址或做类似的事情。
例如,如果您有一个类CQQC
(为简洁和唯一性而选择的名称),它存储三个int
s 和 a 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
创建其“默认值”类的编译时静态实例(或发出错误,如果该类不是 a 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>
and ,并根据 的值在andstatic_assert
之间有条件地切换。请注意,即使这样做了,任何生成的 C++03 代码都可能不如 C++11 等效代码那么高效,因为编译器可能不会优化成品中的变量,也不会优化成品中的变量。constexpr
const
__cplusplus
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>
and static_assert()
in #if __cplusplus >= 201103L
...#endif
块,更改Flags::DEFAULTS
inCQQC::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()
从这个问题中使用,因为它比我自己写的要快。