【问题标题】:Default value for a c++ class member variable : referring to it laterc++ 类成员变量的默认值:稍后参考
【发布时间】:2017-03-12 20:19:38
【问题描述】:

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

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

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

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

【问题讨论】:

  • 将值放入构造函数初始化列表中。
  • 我被允许c++ <= 2003,我完成了我的问题

标签: c++ constructor default-value c++03 member-variables


【解决方案1】:

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

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

现代 C++ 允许构造函数继承,其中一个构造函数可以调用(基本)构造函数,例如 here。

【讨论】:

  • 这如何回答我的问题?
【解决方案2】:

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

例如,如果您有一个类 CQQC(为简洁和唯一性而选择的名称),它存储三个 ints 和一个 std::string,默认值为 0、42、@ 987654332@ 和 "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;

看到它在行动here。


我不确定这样的事情有多普遍,但它的好处是它为默认值提供了一个通用接口,同时仍然允许您将每个类的默认值持有者放在类的标题中。

使用上面的例子:

// 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,无需任何重大修改,通过使用宏有条件地启用&lt;type_traits&gt; 和static_assert,并有条件地在constexpr 之间切换和const,基于__cplusplus 的值。请注意,即使这样做了,任何生成的 C++03 代码都可能不如 C++11 等效代码那么有效,因为编译器可能不会优化成品中的 const 变量以及优化 @987654346 @ 出局。

为此,我们需要定义一些 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

然后,我们只需要将 &lt;type_traits&gt; 和 static_assert() 包装在 #if __cplusplus &gt;= 201103L ... #endif 块中,在 CQQC::set_i(Flags) 中更改 Flags::DEFAULTS 因为枚举在 C++ 之前没有引入自己的范围11(我只是将其更改为CQQC::DEFAULT,因为我想保持它的范围以澄清它不是宏),并处理一两个微小的语法问题(例如&lt;::CQQC&gt;在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 在使用-O3 生成的目标文件中仍有Defaults 的符号。目前无法与 MSVC 相比,我的系统上没有安装 2015,而且由于我不知道在线 MSVC 编译器在哪里存储临时目标文件,所以我无法在线dumpbin /symbols它们。]

literal_string::length() 用于this 问题,因为它比我自己写的要快。

【讨论】:

  • 确实很好,但正如我在问题中所写,我可以c++ &lt;= c++03,而constexpr 是c++ &gt;= c++11...
  • 哦,我错过了。等一下,我看看它是否适用于普通的const,如果有必要的话重写一下。
  • @user10000100_u ...并且 1) 向 literal_string 添加了一个构造函数,该构造函数采用 const char* const,并且 2) 使用了一点预处理器魔法使其与 C++03 兼容。 (注意在C++03中可能效率较低,取决于编译器是否能优化出static const成员变量。)
  • 我会仔细查看您的代码(再次感谢),然后再回复您!
【解决方案3】:

这是一个适用于 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 成员的类。优点是您可以链接设置器,因此您可以显式设置一些成员,而将其余成员保留为默认值。这被称为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;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-11
    • 1970-01-01
    • 2021-08-26
    • 1970-01-01
    • 1970-01-01
    • 2020-11-27
    相关资源
    最近更新 更多