0

我有一个C具有成员变量的类,因此每个成员变量在未设置时都必须设置为默认值。我有很多成员变量C,以及设置一些变量而不设置其他变量等的各种构造函数,以便确保必须具有默认值的成员变量设置为其默认值,我依赖一个void Init()成员函数:在Init我将所有具有默认值的成员变量设置为其默认值,并调用Init我的构造函数等。

现在,我必须稍后在我的代码中引用默认值,通常是为了知道客户端是否通过 setter 将它们设置为不同于默认值的其他值,以便我可以触发一种或另一种行为。

我的问题是:实现“成员变量的默认值”概念的最佳方法是什么?通过在标头中定义的常量声明C? 作为const成员变量?作为static const成员变量?

备注:我可以c++ <= 2003

4

3 回答 3

0

如果您有“各种构造函数”,那么您必须在构造函数列表中添加未在构造函数代码中初始化的成员变量的初始化。

如果这感觉很麻烦,您可以在一个常见的初始化函数中将其分解。

现代 C++ 允许您构造构造函数继承,其中一个构造函数可以调用(基)构造函数,如这里

于 2016-10-30T17:01:55.470 回答
0

存储默认值的一种方法是作为constexpr static特制的实例LiteralType,允许您检查其成员。任何可以在编译时构造的成员变量都很容易存储,但是您需要为constexpr仅运行时的类型(例如std::string. 然后,当告诉编译器优化代码时,它应该能够完全删除“默认值”实例,除非您获取它的地址或做类似的事情。

例如,如果您有一个类CQQC(为简洁和唯一性而选择的名称),它存储三个ints 和 a std::string,具有默认值042359"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 等效代码那么高效,因为编译器可能不会优化成品中的变量,也不会优化成品中的变量。constexprconst__cplusplusconstconstexpr

为此,我们需要定义一些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::DEFAULTSinCQQC::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()这个问题中使用,因为它比我自己写的要快。

于 2016-10-30T21:58:43.023 回答
0

这是一个适用于 C++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;
}
于 2016-10-31T09:15:24.897 回答