我有一个具有成员变量的类C
,这样每个变量都必须设置为默认值。我在C
中有很多成员变量,以及设置一些变量但不设置其他变量的各种构造函数,以确保必须具有默认值的成员变量设置为默认值,我依赖void Init()
成员函数:在Init
中我将所有具有默认值的成员变量设置为默认值,并在构造函数中调用Init
等。
现在,我必须在我的代码中稍后引用默认值,通常要知道客户端是否通过setter将它们设置为其默认值以外的其他值,以便我可以触发一种或另一种行为。 / p>
我的问题是:实现概念的最佳方式是什么"成员变量的默认值" ?通过标题中定义的常量来声明C
?作为const
成员变量?作为static const
成员变量?
备注:我被允许c++ <= 2003
。
答案 0 :(得分:0)
如果你有&#34;各种构造者&#34;那么你必须在初始化列表的参数的代码中添加未初始化的成员变量的初始化。
如果这感觉很麻烦,你可以在常见的初始化函数中将其分解出来。
Modern C ++允许构造函数继承,其中一个构造函数可以调用here之类的(基础)构造函数。
答案 1 :(得分:0)
存储默认值的一种方法是作为特制constexpr static
的{{1}}实例,允许您检查其成员。可以在编译时构造的任何成员变量都很容易存储,但是您需要为仅运行时类型(例如LiteralType
)创建constexpr
可构造包装器。然后,当告诉编译器优化代码时,它应该能够完全删除“默认值”实例,除非你获取它的地址或做某种事情。
例如,如果您有一个类std::string
(为简洁和唯一性而选择的名称),它会存储三个CQQC
和一个int
,默认值为{{1 }},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
在行动here中查看。
我不确定这样的事情是多么常见,但关于它的好处是它提供了默认值的通用接口,同时仍然允许你将每个类的默认持有者放在类的标题中。 / p>
使用上面的例子:
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
的值。请注意,即使这样做,任何生成的C ++ 03代码可能都不如C ++ 11等效,因为编译器可能无法优化成品中的const
变量以及优化__cplusplus
出来了。
为此,我们需要定义一些const
辅助宏,而不是直接使用关键字,并更改C ++ 03或更早版本的样板宏。 (因为后者需要更改,无论如何,不需要在其中使用辅助宏。):
constexpr
然后,我们只需要在constexpr
... // 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
因为枚举不会在C ++ 11之前引入自己的范围(我只是将它改为Flags::DEFAULTS
,因为我想保持它的范围,以澄清它不是一个宏),并处理一个或两个微小的语法问题(例如CQQC::set_i(Flags)
在C ++ 11中有效,但不是C ++ 03,通过添加空格来修复),瞧:
CQQC::DEFAULT
[使用GCC 5.3.1 20151207测试,C ++ 03在使用<::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;
的符号。目前无法与MSVC进行比较,我没有在这个系统上安装2015,并且由于我不知道在线MSVC编译器存储临时目标文件的位置,我不能Defaults
他们在线。] < / p>
从this问题中使用的
-O3
,因为它比写我自己的更快。
答案 2 :(得分:0)
这是一个在C ++ 03中运行的ideea:
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
你必须做更多的工作,但它完全符合你的要求。
当然,您可以根据自己的需要调整和/或扩展它。
ideea很简单。我们有一个Default_value
模板类。这允许我们明确设置所需的默认值,并跟踪值是否从默认值更改。
然后我们有X_init
专门用于初始化X
成员的课程。优点是您可以链接setter,因此您可以显式设置一些成员,同时将其余成员保留为默认值。这称为named parameter idiom
此方法的缺点是您在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;
}