In the following code snippet, I defined a nested class Inner
inside an encloding class Outer
. The inner class is placed inside a private section at the end of the enclosing class.
class Outer {
public:
Outer(): in(new Inner()) { // Case 1: OK
cout << "Outer ctor is called." << endl;
}
void f() {
Inner in2; // Case 2: OK
cout << "f() is called." << endl;
}
Inner *in1; // Case 3: error: 'Inner' does not name a type
void g(Inner in3) { // Case 4: error: 'Inner' does not name a type
cout << "g() is called." << endl;
}
int y = x 2; // Case 5: OK to use a data member x defined in a later section
private:
class Inner {};
Inner *in;
int x = 1;
};
int main() {
Outer out;
out.f();
return 0;
}
Let's consider the following 5 cases:
- Case 1: use the
Inner
class in the constructor initializer list -- OK! - Case 2: use the
Inner
class as a type to define a local variable inside a member function -- OK! - Case 3: use the
Inner
class as a type to define a data member -- error: 'Inner' does not name a type - Case 4: use the
Inner
class as a method type -- error: 'Inner' does not name a type - Case 5: useage of a regular class data member is allowed before the definition of the data member in a different section.
I understand the errors I got are related to the declaration order: the Inner
class's declaration/definition appers after the public section that uses it. So if we switch the order of the private and public sections such that the Inner
class definition appears at the begining of the enclosing class, then all errors are gone. This is also pointed out by the answer to a similar question Can't use public nested class as private method parameter. But it is still unclear to me:
Why Case 1-2 work fine but Case 3-4 failed with error? what are the differences between Cases 1-2 and Cases 3-4? I know this is related to the declration order of the Inner class relative to the code that uses it. But why does the declaration order matter in some cases and not matter in other cases?
For Case 5 involving two regular class data members
x
andy
defined in two different sections (one public, one private), it is fine to use the data memberx
byy
even before x's definition. In more genral terms, no matter what the relative order of two (public/private) sections is, the code in one section may access the data members defined in the other section. But in the case of nested class member, we may run into errors if we use a nested class before its declaration section (such as in Case 3-4). So why do we have such a difference between a regular class member and a nested class member?
CodePudding user response:
Thanks to @NathanPierson's comments and thoughtful discussions, I have done some more research and I decide to provide my own answer.
First of all, let me describe some facts about C compilation:
When compiling a class, the compiler goes from top to bottom, only once, analyzing all declarations inside the class body. This includes declarations of data members and member functions.
The declaration of a member function includes the parameter types, return types, and function name. But the body of a member function is not part of a declaration, and it belongs to the function definition.
Quoted from IBM documentation:
The body of a member function is analyzed after the class declaration so that members of that class can be used in the member function body, even if the member function definition appears before the declaration of that member in the class member list.
In other words, the definition of the member function is deferred until all class declarations are complete, thus, the member function body is free to use all class members, regardless of whether the class members are declared before or after the member function.
- Quoted to from C Primer, 5th Edition, p. 279:
A class must be defined — not just declared — before we can write code that creates objects of that type. Otherwise, the compiler does not know how much storage such objects need.
Now back to the code:
Cases 1-2 are OK. This is because the definition of the member function body is deferred after all class declarations, so it is fine to use any class members, including a nested class, in the member function body.
Case 3 is in error. This is immediately explained by fact 1 that the compiler goes from top to bottom. When the compiler reaches the line of code in Case 3, it hasn’t seen any definition for type
Inner
, so we get an error: 'Inner' does not name a type.Case 4 is in error:
Inner
is used as a parameter type for the member functiong(Inner)
, so parameter typeInner
is part of the member function declaration. Then according to Fact 4, it requires that the typeInner
must have already been defined, which is obviously not true becauseInner
’s definition appears after.Case 5 (
int y = x 2
) runs into undefined behaviour because the variable x is accessed before it is initialized.
CodePudding user response:
Case 1 and 2 have the variable in the function body (including member initialization in that) while case 3 and 4 goes towards the interface of the class. The body of the functions are compiled after the whole class declaration is parsed so use of the Inner
type works there. But the member variable and function argument Inner
is part of the class declaration and can only have already known types.
It's a bit like the "hoisting" except you hoist both variables and functions to the top.
When you write Outer() : in(new Inner) { }
then that turns into:
class Outer {
Outer();
class Inner {};
Inner *in;
};
Outer::Outer() : inner(new Inner) { }
as you can see in that form the Inner
class is declared before you use it.
That should also explain how having multiple public and private sections has no effect on the whole thing. The whole class gets declared fully before any of the access checks have to be done in the function bodies.
Just reorder things so they happen in the right order:
class Outer {
class Inner {};
public:
Outer(): in(new Inner()) { // Case 1: OK
cout << "Outer ctor is called." << endl;
}
void f() {
Inner in2; // Case 2: OK
cout << "f() is called." << endl;
}
Inner *in1; // Case 3: error: 'Inner' does not name a type
void g(Inner in3) { // Case 4: error: 'Inner' does not name a type
cout << "g() is called." << endl;
}
private:
Inner *in;
};
int main() {
Outer out;
out.f();
return 0;
}