简体   繁体   中英

How Do Member Order Dependencies Influence Initialization Lists In C++?

When I read the post https://isocpp.org/wiki/faq/ctors#empty-parens-in-object-decl , it has a sample code.

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}

at the subsection part Is it moral for one member object to be initialized using another member object in the initializer expression? and What if one member object has to be initialized using another member object? ,it said that:

"In a constructor's initialization list, it is easiest and safest to avoid using one member object from this object in the initialization expression of a subsequent initializer for this object. Because of this guideline, the constructor that follows uses s.len_ + 1u rather than len_ + 1u, even though they are otherwise equivalent.The s. prefix avoids an unnecessary and avoidable order dependency."

<-- why are s.len_ and len_ equivalent? s.len_ refer to s 's len_ , but len_ refer to this ; And in the question I asked days ago why copy constructor use private property directly in C++ , LogicStuff said that accessing s.len_ works because it adapts to the const instance. It seems the two answers are unrelated.

<-- why does it say that s. prefix avoids an unnecessary and avoidable order dependency? Can any one explain order dependency in this situation in detail?

why are s.len_ and len_ equivalent?

They're equivalent in value . len_ simply copies the value of s.len_ in the initialization list, so they'll both store the same size.

why does it say that s. prefix avoids an unnecessary and avoidable order dependency? Can any one explain order dependency in this situation in detail?

This relates to an order dependency between the members of a class/struct and its initialization list in the constructor. For example, this is incorrect:

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

This is correct:

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

This initialization order can be a source of mistakes. Any kind of changing/reordering of the class's member variables requires updating the initialization lists of the constructor to match the new order. We can't avoid that.

However, in the example listed (version #1):

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

... if we did this (version #2):

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

... we would then have two separate places in the initialization list depending on the order in which len_ is initialized, not just one. This code immediately above (#2) would be fine, but it's more prone to human mistakes if you ever edit the class data members afterwards.

Because of that, the former version (#1) reduces the number of places that depend on initialization order, and therefore makes it easier to avoid mistakes (present and future).

Can any one explain order dependency detailedly in this situation?

len_ is declared before data_ in your class definition. Thus len_ will always be initialized first, regardless of the order of the items in the initialization list.

The order that your member variables appear in your class (top-down), is the order that they will be initialized. The initialization list does not change the ordering.

So what would happen if len_ appears after data_ in your class, and what problems can occur? Let's see:

If your class looked like this:

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

and then you did this:

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);
}         

A bug is introduced, since len_ is not yet initialized when used in the call to new char[] , even though the initializer list happens to mention len_ first. That's why using s.len_ is guaranteed to work in the initializer list, regardless of the order of declaration of the member variables.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM