Patrik Valkovič
Patrik Valkovič

Reputation: 724

Assignment operation in member initializer lists

I have the following Stack class.

class Stack{
public:
    int size;
    int* x;
    Stack() : size(10), x(new int[10]) {}
    Stack(const Stack& s) : x(new int[size=s.size]) {}

};

Notice the assignment in the copy constructor. The code works, compile fine and compiler (gcc) doesn't complain even with -Wall -Wextra flags. Does the compiler automatically rewrite the compiler to this?

Stack(const Stack& s) : size(s.size), x(new int[size]) {}

Or is there some other magic? I noticed when I change the definition order, the compiler complaints about not-in-order initialization. So I assume it's the case I mentioned. I couldn't find anything in the documentation and the ASM output doesn't help me either.

Upvotes: 2

Views: 257

Answers (4)

NathanOliver
NathanOliver

Reputation: 180415

Does the compiler automatically rewrite the compiler to this?

Stack(const Stack& s) : size(s.size), x(new int[size]) {}

No.

Stack(const Stack& s) : x(new int[size=s.size]) {}

can be though of as being

Stack(const Stack& s) : size(), x(new int[size=s.size]) {}

but it isn't really since actually writing size() would value initialize it, meaning it is zero initialized, but since it is the compiler that synthesizes the initialization, default initialization[1] happens meaning it is not initialized. Then you assign a value to it in x's initialization. This is "safe", meaning it works in this case, but I wouldn't recommend it it.

Stack(const Stack& s) : size(s.size), x(new int[s.size]) {}

initializes both members and if you ever change the order of them in the class, you will still have the correct behavior.

Upvotes: 4

Members of a class are always initialized in order of declaration, regardless of the order you specify them in the member initialization list. And if you omit them, they are default initialized. So doing this:

Stack(const Stack& s) : x(new int[size=s.size]) {}

Means that size is default initialized first. That leaves it with an indeterminate value (as fundamental types are supposed to be default-initialized). Then the initializer for x is evaluated. Part of evaluating new int[size=s.size] involves the assignment expression size=s.size, which modifies size as a side effect. So your code is correct here, despite being likely to raise eyebrows.

When you switch the order of the members around, then the assignment happens before size is supposed to be initialized. That leaves your code code open to undefined behavior.

Upvotes: 2

Quimby
Quimby

Reputation: 19113

No, it rewrites it to this:

class Stack{
public:
    int size;
    int* x;
    Stack() : size(10), x(new int[10]) {}
    Stack(const Stack& s) :size(), x(new int[size=s.size]) {}
};

size() would be a default constructor for the object size but ints don't have one, so its just an empty statement and size remains uninitialized. Or does it?

I would say this code might generate undefined behavior. The member variables are initialized in the order in which they are declared as per 10.9.2 Initializing bases and members. But the evaluation order of the expressions used for the initialization is not defines. So, size=s.size can be called either before or after size(). For class types that would be a problem, but I am not sure whether size() is guaranteed to be no-op and whether the compiler might decide to initialize the variable to e.g. 0.

Upvotes: 0

Botje
Botje

Reputation: 30807

From the Cppreference.com page on constructors:

Names that appear in expression-list or brace-init-list are evaluated in scope of the constructor:

So yes, size here refers to this->size in the scope of the constructor, and the assignment size=s.size is a valid expression.

It goes without saying that you should not expect this to pass a code review :)

Upvotes: 0

Related Questions