【问题标题】:How Do Member Order Dependencies Influence Initialization Lists In C++?成员顺序依赖关系如何影响 C++ 中的初始化列表?
【发布时间】:2015-11-24 18:00:48
【问题描述】:

当我阅读https://isocpp.org/wiki/faq/ctors#empty-parens-in-object-decl 的帖子时,它有一个示例代码。

class MyString {
public:
  MyString(const MyString& s);              // copy constructor
  // ...
protected:
  unsigned len_;  // ORDER DEPENDENCY
  char*    data_;  // ORDER DEPENDENCY
};
MyString::MyString(const MyString& s)
  : len_ (s.len_)
  , data_(new char[s.len_ + 1u])       
{                  ↑↑↑↑↑↑   // not len_
  memcpy(data_, s.data_, len_ + 1u);
}                        ↑↑↑↑   // no issue using len_ in ctor's {body}

Is it moral for one member object to be initialized using another member object in the initializer expression?What if one member object has to be initialized using another member object?的小节部分,它说:

"在构造函数的初始化列表中,避免在该对象的后续初始化程序的初始化表达式中使用来自该对象的一个​​成员对象是最简单和最安全的。因为这个准则,后面的构造函数使用 s.len_ + 1u 而不是 len_ + 1u,即使它们在其他方面是等价的。s. 前缀避免了不必要的和可避免的顺序依赖。”

s.len_ 和len_ 是等价的? s.len_指的是slen_,但len_指的是this;在我几天前why copy constructor use private property directly in C++ 提出的问题中,LogicStuff 说访问 s.len_ 有效,因为它适应了const 实例。这两个答案似乎无关。

s. 前缀避免了不必要的和可避免的顺序依赖?任何人都可以详细解释这种情况下的订单依赖性吗?

【问题讨论】:

    标签: c++ coding-style


    【解决方案1】:

    为什么 s.len_ 和 len_ 是等价的?

    它们在上是等价的。 len_ 只是将 s.len_ 的值复制到初始化列表中,因此它们将存储相同的大小。

    为什么这么说。前缀避免了不必要和可避免的 订单依赖?任何人都可以在此解释订单依赖性 详细情况?

    这涉及到class/struct 的成员与其在构造函​​数中的初始化列表之间的顺序依赖关系。例如,这是不正确的:

    struct Foo
    {
        int x, y;
        Foo(): y(0), x(0) {} // wrong initialization order
    };
    

    这是正确的:

    struct Foo
    {
        int x, y;
        Foo(): x(0), y(0) {} // right initialization order
    };
    

    此初始化顺序可能会导致错误。类成员变量的任何类型的更改/重新排序都需要更新构造函数的初始化列表以匹配新顺序。我们无法避免。

    但是,在列出的示例中(版本 #1):

    MyString::MyString(const MyString& s)
      : len_ (s.len_)
      , data_(new char[s.len_ + 1u])    
    

    ...如果我们这样做(版本 #2):

    MyString::MyString(const MyString& s)
      : len_ (s.len_)
      , data_(new char[len_ + 1u])    
    

    ...根据len_ 的初始化顺序,我们将在初始化列表中有两个不同的位置,而不仅仅是一个。上面的代码(#2)会很好,但是如果您之后编辑类数据成员,它更容易出现人为错误。

    正因为如此,前一个版本(#1)减少了依赖于初始化顺序的地方的数量,因此更容易避免错误(现在和未来)。

    【讨论】:

    • 为什么s可以直接访问len_? len_ 是私有属性。
    • 哦,这很好,因为它是MyString 访问MyString。你可以粗略地把它想象成“班级永远是自己的朋友”。您可以从类方法中访问完全相同的类的其他实例的数据成员。
    • 请查看答案的评论:stackoverflow.com/questions/33844477/…。我认为你是对的,但 LogicStuff 不这么认为。
    • 哦,LogicStuff 在那里是正确的。在这里,它与私人访问无关(这是合法的)。它与 const 正确性有关。 getFreq() 不能通过 const node&const node* 调用,无论您从哪里调用(甚至在类本身内)。
    • 说得非常简单/粗略,每当你看到const T&const T*,其中T 是任何数据类型,你只能在它上面调用成员函数,最后也有const .此外,如果您位于末尾有 const 的成员函数中,则只能在标记为 const 的同一对象中调用成员函数(只读)。这是为了确保当你有一些应该是只读的东西时,你只调用同样标记为只读的函数。
    【解决方案2】:

    任何人都可以详细解释这种情况下的订单依赖关系吗?

    len_ 在您的类定义中在 data_ 之前声明。因此len_总是首先被初始化,而不管初始化列表中项目的顺序如何。

    您的成员变量出现在您的类中的顺序(自上而下),就是它们将被初始化的顺序。初始化列表不会改变顺序。

    那么如果len_ 在你的班级出现在data_ 之后会发生什么,会出现什么问题呢?让我们看看:

    如果你的班级看起来像这样:

    class MyString {
    public:
      MyString(const MyString& s);              // copy constructor
      // ...
    protected:
      char*    data_;    
      unsigned len_;  
    };
    

    然后你这样做了:

    MyString::MyString(const MyString& s)
      : len_ (s.len_)  
      , data_(new char[len_ + 1u])  // using uninitialized len_ here.     
    { 
       // now len_ is initialized and can be used.                 
       memcpy(data_, s.data_, len_ + 1u);
    }         
    

    引入了一个错误,因为在调用new char[] 时使用len_ 时尚未初始化,即使初始化列表碰巧首先提到了len_。这就是为什么使用 s.len_ 可以保证在初始化列表中工作,而不管成员变量的声明顺序如何。

    【讨论】:

    • 这是否意味着初始化完成,直到初始化列表结束?
    • 所有可以默认初始化的变量都在你的类定义中自上而下地被初始化,在进入构造函数的主体之前。初始化的顺序总是按照声明变量的顺序,这是底线。初始化器列表不能改变这种行为,这就是为什么在初始化器列表中使用成员时必须小心。
    猜你喜欢
    • 2011-04-14
    • 2011-09-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-23
    • 2011-12-01
    • 1970-01-01
    相关资源
    最近更新 更多