デフォルト値を保存する 1 つの方法constexpr static
は、特別に細工された のインスタンスとして、LiteralType
そのメンバーをチェックできるようにすることです。コンパイル時に構築できるメンバー変数は、簡単に格納できますがconstexpr
、std::string
. 次に、コンパイラがコードを最適化するように指示されると、アドレスを取得するか、そのようなことをしない限り、「デフォルト値」インスタンスを完全に削除できるはずです。
たとえば、デフォルト値、、、およびで 3 つの と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
デフォルト値を見ることができるという点で、優れたレベルのカプセル化を提供します。
<type_traits>
これで、マクロを使用して と を条件付きで有効にし、 の値に基づいてとstatic_assert
を条件付きで切り替えることにより、大きな変更を加えることなく、このコードを C++03 との後方互換性を持たせることができます。これが行われたとしても、結果として得られる 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
次に、列挙型は C++11 より前に独自のスコープを導入しないため、ラップ<type_traits>
しstatic_assert()
て#if __cplusplus >= 201103L
...#endif
ブロック内で変更Flags::DEFAULTS
するだけです (私はそれを に変更しました。マクロ)、1 つまたは 2 つの小さな構文の問題 ( C++11 では有効であるが、スペースを追加することで修正される C++03 では有効でないなど) に対処すると、出来上がり:CQQC::set_i(Flags)
CQQC::DEFAULT
<::CQQC>
// 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()
自分で書くよりも速かったので、この質問から使用されました。