开发者

When do I have to use initializer lists for initializing C++ class members?

开发者 https://www.devze.com 2022-12-09 19:28 出处:网络
let\'s say I have std::map< std::string, std::string > m_someMap as a private member variable of class A

let's say I have std::map< std::string, std::string > m_someMap as a private member variable of class A

Two questions: (and the only re开发者_C百科ason I'm asking is because I came across code like that)

  1. What's the purpose of this line:

    A::A() : m_someMap()
    

    Now I know that this is intialization, but do you have to do this like that? I'm confused.

  2. What's the default value of std::map< std::string, std::string > m_someMap, also C# defines that int, double, etc. is always initialized to defualt 0 and objects are to null (at least in most cases) So what's the rule in C++?? are object initialized by defualt to null and primitives to garbage? Of course I'm taking about instance variables.

EDIT:

also, since most people pointed out that this is a style choice and not necessary, what about:

A::A() : m_someMap(), m_someint(0), m_somebool(false)


m_somemap

  1. You don't have to.
  2. What you get if you omit it: An empty std::map< std::string, std::string >, i.e., a valid instance of that map which has no elements in it.

m_somebool

  1. You have to initialize it to true or false if you want it to have a known value. Booleans are "plain old data types" and they do not have the concept of a constructor. Moreover, the C++ language does not specify default values for non-explicitly-initialized booleans.
  2. What you get if you omit it: a boolean member with an unspecified value. You must not do this and later use its value. Because of that, it is a strongly recommended policy that you initialize all values of this type.

m_someint

  1. You have to initialize it to some integer value if you want it to have a known value. Integers are "plain old data types" and they do not have the concept of a constructor. Moreover, the C++ language does not specify default values for non-explicitly-initialized integers.
  2. What you get if you omit it: an int member with an unspecified value. You must not do this and later use its value. Because of that, it is a strongly recommended policy that you initialize all values of this type.


There is no need to actually do it.
The default constructor will autmatically do it.

But somtimes by making it explicit it acts as sort of documentation:

class X
{
    std::map<string,string>  data;
    Y                        somePropertyOfdata;

    X()
      :data()                    // Technically not needed
      ,somePropertyOfdata(data)  // But it documents that data is finished construction
    {}                           // before it is used here.
};

The rule in C++ is that unless you explicitly initialise POD data it is undefined while other classes have there default constructor called automatically (even if not explicitly done so by the programmer).

But saying that. Consider this:

template<typename T>
class Z
{
     T  data;   
     Z()
        :data()    // Technicall not need as default constructor will
                   // always be called for classes.
                   // But doing this will initialize POD data correctly
                   // if T is a basic POD type. 
     {}
};

Here you would exepect data to be default initialized.
Technically POD do not have constructors so if T was int then would you expect it to do anything? Becuase it was explicitly initialize it is set to 0 or the equivalent for POD types.

For the edit:

class A
{
    std::map<string,string>   m_someMap;
    int                       m_someint;
    bool                      m_somebool;
   public:
    A::A()
       : m_someMap()      // Class will always be initialised (so optional)
       , m_someint(0)     // without this POD will be undefined
       , m_somebool(false)// without this POD will be undefined
    {}
};


As others pointed out: it is not necessary but more or less a matter of style. The upside: it shows that you explicitly want to use the default constructor and makes your code more verbose. The downside: If you have more than one ctor it can be a pain to maintain changes in all of them and sometimes you add class members and forget to add them to the ctors initializer list and make it look inconsistent.


A::A() : m_someMap()

This line is unnecessary in this case. However, in general, it is the only proper way to initialize class members.

If you have a constructor such as this:

X() : y(z) {
 w = 42;
}

then the following happens when the X constructor is called:

  • First, all members are initialized: for y, we explicitly say we wish to call the constructor which takes a z as its argument. For w, what happens depends on the type of w. If w is a POD type (that is, basically a C-compatible type: No inheritance, no constructors or destructors, all members public, and all members are POD types as well), then it is not initialized. Its initial value is whatever garbage was found at that memory address. If w is a non-POD type, then its default constructor is called (non-POD types are always initialized on construction).
  • Once both members have been constructed, we then call the assignment operator to assign 42 to w.

The important thing to note is that all constructors are called before we enter the body of the constructor. Once we're in the body, all members have already been initialized. So there are two possible problems with our constructor body.

  • What if w is of a type that doesn't have a default constructor? Then this won't compile. Then it must be explicitly initialized after the :, like y is.
  • What if this sequence of calling both default constructor and assignment operator is needlessly slow? Perhaps it'd be much more efficient to simply call the correct constructor to begin with.

So in short, since m_someMap is a non-POD type, we don't strictly speaking need to do : m_someMap(). It would have been default constructed anyway. But if it had been a POD type, or if we had wanted to call another constructor than the default one, then we would have needed to do this.


Just to be clear about what's happening (with regards to your 2nd question)

std::map< std::string, std::string > m_someMap creates a stack variable called m_someMap and the default constructor is called on it. The rule for C++ for all of your objects is if you go:

T varName;

where T is a type, varName is default constructed.

T* varName;

should be explicitly assigned to NULL (or 0) -- or nullptr in the new standard.


To clarify the default value issue:

C++ does not have the concept of some types implicity being by-reference. Unless something is explicitly declared as a pointer, it cannot ever take a null value. This means that every class will have a default constructor for building the initial value when no constructor parameters are specified. If no default constructor is declared, the compiler will generate one for you. Also, whenever a class contains members that are of classed types, those members will be implicitly initialized via their own default constructors at object construction, unless you use the colon syntax to explicitly call a different constructor.

It just so happens that the default constructor for all STL container types builds an empty container. Other classes may have other conventions for what their default constructors do, so you still want to be aware that they're being invoked in situations like this. That's why the A::A() : m_someMap() line, which is really just telling the compiler to do what it would do already anyway.


When you create an object in C++, the constructor goes through the following sequence:

  1. Call the constructors of all parent virtual classes in the entire class tree (In an arbitrary order)
  2. Call the constructors of all directly inherited parent classes in the order of declaration
  3. Call the constructors of all member variables in the order of declaration

There are a few more specifics than this, and some compilers allow you to force a few things out of this specific order, but this is the general idea. For each of these constructor calls you can specify the constructor arguments, in which case C++ will call the constructor as specified, or you can leave it alone and C++ will try to call the default constructor. The default constructor is simply the one that takes no arguments.

If any of your virtual parent classes, non-virtual parent classes, or member variables don't have a default constructor or need to be created with something other than the default, you add them to the list of constructor calls. Because C++ assumes a default constructor call, there is absolutely no difference between putting a default constructor in the list and leaving it out entirely (C++ will not (unless under special circumstances outside the scope of this question) create an object without a call to a constructor of some sort). If a class does not have a default constructor and you do not provide a constructor call, the compiler will throw an error.

When it comes to built in types such float or int, the default constructor does nothing at all, and so the variable will have the default value of whatever was left in the piece of memory. All built in types also have a copy constructor so you can you can initialize them by passing their initial value as the only argument to the variable's constructor.

0

精彩评论

暂无评论...
验证码 换一张
取 消