C ++初始化列表

时间:2016-07-06 11:41:42

标签: c++ const-correctness initialization-list

这是我的第一篇文章。我相信我知道stackoverflow的最佳实践,但可能不是100%。我相信没有具体的帖子可以解决我的讯问;我也希望它不会太模糊。

我试图找出编写C ++构造函数的好习惯 从事中到重型工作。

将(全部?)init工作推入初始化列表似乎是一个好主意 我想到了两个原因,即:

  • 资源获取是初始化

    据我所知,保证会员的最简单方法 在资源获取时正确初始化是为了确保 初始化列表的括号内的内容是正确的 评估时

    class A
    {
      public:
        A(const B & b, const C & c)
        : _c(c)
        {
            /* _c was allocated and defined at the same time */
            /* _b is allocated but its content is undefined */
            _b = b;
        }
      private:
        B _b;
        C _c;
    }
    
  • const班级成员

    使用初始化列表是唯一正确的使用方法 可以保存实际内容的const个成员。

    class A
    {
      public:
        A(int m, int n, int p)
        : _m(m) /* correct, _m will be initialized to m */
        {
            _n = n; /* incorrect, _n is already initialized to an undefined value */
            *(const_cast<int*>(&_p)) = p; /* technically valid, but ugly and not particularly RAII-friendly */
        }
      private:
        const int _m, _n, _p;
    }
    

但是有些问题似乎会影响初始化列表的使用:

  • 顺序

      

    成员变量总是按照它们在类定义中声明的顺序进行初始化,因此在构造函数初始化列表中按顺序写入它们。以不同的顺序编写它们只会使代码混乱,因为它不会按照您看到的顺序运行,这会使得很难看到依赖于顺序的错误。

         

    http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#S-discussion

    如果使用值初始化值,这很重要 先前在列表中初始化。例如:

    A(int in) : _n(in), _m(_n) {}
    

    如果在_m之前定义_n,则初始化时的值未定义。

    我准备在我的代码中应用此规则,但在工作时 与其他人一起导致代码冗余并强制阅读 一次两个文件。 这是不可接受的,并且有些容易出错。

    解决方案 - 仅使用来自ctor参数的数据进行初始化。

    解决方案的问题 - 在没有的情况下保持初始列表中的工作 内在依赖意味着重复操作。例如:

    int in_to_n(int in)
    {
        /* ... */
        return n;
    }
    
    int modify(int n)
    {
        /* ... */
        return modified;
    }
    
    A::A(int in)
    : _n(in_to_n(in))
    , _n_modified(modify(in_to_n(in)))
    {}
    

    对于一些重复操作,我相信编译器 可以重复使用现有数据,但我认为不应该依赖于此 对于重要的工作(我甚至不认为如果打电话就完成了 非内联单独代码)。

  • 你可以在列表中放多少工作?

    在前面的例子中,我调用函数来计算它的内容 属性将被初始化为。这些可以是普通/ lambda 函数或静态/非静态方法, 当前班级或其他班级。 (我不建议使用当前班级的非静态方法, 根据标准,它甚至可能是未定义的用法,不确定。)

    我想这本身并不是一个大问题,但需要制作 特别努力清晰,以保持代码的意图,如果 写那些以这种方式做大工作的大班。

    此外,在尝试将解决方案应用于上一个问题时, 在初始化时,你可以做很多独立的工作 你的实例......如果你有一个很长的序列,这通常会变大 用内部依赖项初始化的属性。 它开始看起来只是程序,翻译成一个 初始化列表;我想这不是C ++应该是什么 过渡到?

  • 多个插件

    通常会同时计算两个变量。设置两个变量 在init列表中立即表示:

    • 使用丑陋的中间属性

      struct InnerAData
      {
          B b;
          C c;
      };
      /* must be exported with the class definition (ugly) */
      
      class A
      {
        public:
          A(const D & input)
          : _inner(work(input))
          , _b(_inner.b)
          , _c(_inner.c) {}
        private:
          B _b;
          C _c;
          InnerAData _inner;
      }
      

      这太糟糕了,并且会强制使用额外无用的副本。

    • 或一些丑陋的黑客

       class A
      {
        public:
          A(const D & input) : _b(work(input)) {}
        private:
          B _b;
          C _c;
          B work(const D & input)
          {
              /* ... work ... */
              _c = ...;
          }
      }
      

      这更加糟糕,甚至无法使用const 或非内置类型属性。

  • 保留内容const

    有时可能需要大部分人来确定价值 给一个属性,以确保它是const, 因此将工作移至初始化列表, 似乎受到限制。我没有举一个完整的例子,但想一想 比如从默认文件名计算数据 从该数据计算完整的文件名,然后检查是否 存在相应的文件来设置const boolean等。

    我认为这不是一个根本问题,但似乎只是一切 直观地在ctor的身体中更清晰,并且移动 它只是为了正确的初始化而在init列表中 const领域似乎有点矫枉过正。也许我只想象事物。

所以这是困难的部分:提出具体问题! 你遇到过类似的问题吗,你找到了更好的解决方案, 如果不是要学习的课程 - 或者是我有什么东西 在这里失踪?

我想我的问题是我几乎想要完成所有的工作 当我可以搜索什么状态的妥协时,到init列表 已启动,稍后会留下一些工作。我只是觉得初始列表 可以在制作现代C ++代码方面发挥更大的作用 我还没有看到它们比基本用法更进一步。

此外,我真的不相信为什么值 按该顺序初始化,而不是按列表的顺序。 我已经口头告诉它了,因为堆栈上的属性是有序的 编译器必须保证堆栈数据永远不会高于SP。 我不确定这是怎样的最终答案......非常确定可以 实现安全的任意重新排序的初始化列表, 如果我错了,请纠正我。

2 个答案:

答案 0 :(得分:0)

在您的代码中:

class A
{
  public:
    A(const B & b, const C & c)
    : _c(c)
    {
        /* _c was allocated and defined at the same time */
        /* _b is allocated but its content is undefined */
        _b = b;
    }
  private:
    B _b;
    C _c;
}

构造函数调用B::B()然后调用B::operator=这可能是一个问题,如果其中任何一个不存在,是昂贵的或没有正确实现RAII和三个规则准则。经验法则是,如果可能的话,总是更喜欢初始化列表。

答案 1 :(得分:0)

从c ++ 11开始,另一种方法是使用委托构造函数:

struct InnerData;
InnerData (work(const D&);

class A
{
  public:
    A(const D & input) : A(work(input)) {}

  private:
    A(const InnerAData&);
  private:
    const B _b;
    const C _c;
};

并且(可以内联,但在标题中可见)

struct InnerAData
{
    B b;
    C c;
};

A::A(const InnerAData& inner) : _b(inner.b), _c(inner.c) {}