C++/CLI constructors initialization sequence  
Author Message
Toutouni





PostPosted: Visual C++ Language, C++/CLI constructors initialization sequence Top

Hi all,

I’ve been translating a C# program to C++/CLI and I saw something that I thought It might or introduce unpredicted results to my code.

A virtual method was called from inside a base class.

That was enough motivation for me to debug the code, and actually find out that it was the virtual method of the derived class which was actually called from the constructor of the base class!

During this debugging session though, I also discovered that the constructor of the base class is called AFTER any initialization of the member variables of the derived class!

I’ve searched for some info for constructing objects in .NET, and I only found this in my first attempts.

Do not call virtual members on an object inside its constructors.

Calling a virtual member causes the most-derived override to be called regardless of whether the constructor for the type that defines the most-derived override has been called. The following code example demonstrates this issue. As the base class constructor executes, it calls the derived class member, even though the derived class constructor has not been called. This example prints BadBaseClass to show that the state field has not been updated by the DerivedFromBad constructor.

At first I stopped reading after I glanced at the headline, because it was clear this was the rule I knew.

Unfortunately the rest of it says that what I saw was correct behavior.

So it is reasonable, If virtual table has to be setup, to prepare derived class before base class.

But this is not valid C++ as far as I know.

The following C++/CLI program produces this call sequence

UtilityItem::UtilityItem(long)

BaseClass::BaseClass()

DerivedClass::TestVirtual()

DerivedClass::DerivedClass() //inside constructor

DerivedClass::TestVirtual()

Instead of this call sequence.

BaseClass::BaseClass()

BaseClass::TestVirtual()

UtilityItem::UtilityItem(long)

DerivedClass::DerivedClass() //inside constructor

DerivedClass::TestVirtual()

Where can I find such information Because It is not easily spotted in “What's New in Visual C++ Compiler, Language, and Tools” help section

Thank you

Ioannis

ref class UtilityItem

{

public:

UtilityItem(long initU)

{

u = initU;

}

private:

long u;

};

ref class BaseClass

{

public:

BaseClass() :

i(-1)

{

d = 1.2;

TestVirtual();

}

private:

long i;

double d;

public:

long intVal()

{

return i;

}

double dblVal()

{

return d;

}

public:

virtual long TestVirtual()

{

return -234;

}

};

ref class DerivedClass : BaseClass

{

public:

DerivedClass() :

ui(gcnew UtilityItem(15)),

ii(-2)

{

dd = 2.3;

TestVirtual();

}

private:

long ii;

double dd;

UtilityItem^ ui;

public:

long intValD()

{

return ii;

}

double dblValD()

{

return dd;

}

public:

virtual long TestVirtual() override

{

return -234;

}

};

int main(array<System::String ^> ^args)

{

Console::WriteLine(L"Hello World");

DerivedClass dd;

long i = dd.intValD();

return 0;

}



Visual C++6  
 
 
Brian Kramer





PostPosted: Visual C++ Language, C++/CLI constructors initialization sequence Top

I would have also expected the BaseClass constructor to be called prior, not before as in your case, to the construction of UtilityItem in C++/CLI.

The C++ version of your code works as I would expect: the base class is constructed first.

C++/CLI does have some differences in how it resolves overloads, but I don't think this falls into that category.

If you don't get an explanation from someone in the know, you should open a bug on this.

Here's the C++/CLI standard.  If this document doesn't specify the behavior of construction order, I doubt any doc will.

Brian

 


 
 
Brian Kramer





PostPosted: Visual C++ Language, C++/CLI constructors initialization sequence Top

Followup. The initialization of an inherited class in C++/CLI should follow whatever standard that C# follows. I tried a C# variation, which instantiates members before base classes (like you're seeing in C++/CLI.) This doesn't fully resolve which of the two approaches is correct however.

namespace test2
{
class X
{
public X()
{
Console.WriteLine("X ctor");
}
}

class Base
{
public Base()
{
Console.WriteLine("Base ctor");
}
}

class Derived : Base
{
public Derived()
{
Console.WriteLine("Derived ctor");
}

X x = new X();
}

class Program
{
static void Main(string[] args)
{
Derived d = new Derived();
}
}
}

This will output:

X ctor
Base ctor
Derived ctor

which is roughly equivalent to your C++/CLI call order.


 
 
Jonathan Caves - MSFT





PostPosted: Visual C++ Language, C++/CLI constructors initialization sequence Top

Yes: unlike in ISO/IEC C++ there is only one virtual function table in C++/CLI and this is initialized before any base-class constructors are called. So if you call a virtual method in a base-class constructor it will call the method in the derived class. The best place to get this information is in the ECMA C++/CLI Standard: which you can download from here:

http://www.ecma-international.org/publications/standards/Ecma-372.htm



 
 
Toutouni





PostPosted: Visual C++ Language, C++/CLI constructors initialization sequence Top

Thank you !


 
 
Toutouni





PostPosted: Visual C++ Language, C++/CLI constructors initialization sequence Top

Hi again,

Just for the reference of it, regarding what troubled me in the initialization sequence in the constructors, I got an answer at “Standard ECMA-335 Common Language Infrastructure (CLI)

http://www.ecma-international.org/publications/standards/Ecma-335.htm

8.9.6.6 Constructors

...

1. Space for the new value is allocated in managed memory.

2. VES data structures of the new value are initialized and user-visible memory is zeroed.

3. The specified constructor for the object type is invoked.

...

Since I used an “initialization list” in my constructors, this may be assumed a “Step 2” phase so,

Constructors for member variables are valid to be called before the constructor of the base class at this point!


 
 
Jonathan Caves - MSFT





PostPosted: Visual C++ Language, C++/CLI constructors initialization sequence Top

No: I would say this is part of step 3 - regardless of the syntax any initializers are run at the beginning of the body of the constructor. In Step two VES means what C++ calls the virtual function table though under the CLR there is more work to do as it also has to support reflection etc.

 
 
Brian Kramer





PostPosted: Visual C++ Language, C++/CLI constructors initialization sequence Top

Jonathan, could you clarify why this CLI design must result in the calling order (as originally posted) to be different

IOW, why can't the C++/CLI and C++ both execute code in the following order:

1. set up the v-table
2. call the base class constructor (process its intializer list, and then enter its constructor)
3. process the initializer list
4. enter the constructor

The fact that steps 2 and 3 gets interchanged in C++/CLI seems odd, especially in light of the fact that the v-table should be there in either case.

Brian


 
 
Toutouni





PostPosted: Visual C++ Language, C++/CLI constructors initialization sequence Top

Dear Jonathan,

I think that if the case in the constructors call sequence was as your claim it to be the result of the following program would be

-1

-1

instead of the actual

0

-1

Should we consider this result a C++/CLI compiler bug then

Thank you for your time

Ioannis

using namespace System;

ref class BaseClass abstract

{

public:

BaseClass() :

i(-1)

{

}

private:

long i;

public:

long BaseInt()

{

return i;

}

};

ref class DerivedClass : BaseClass

{

public:

DerivedClass() :

ii( BaseInt() )

{

}

private:

long ii;

public:

long DerivedInt()

{

return ii;

}

};

ref class SecondDerivedClass : BaseClass

{

public:

SecondDerivedClass()

{

iii = BaseInt();

}

private:

long iii;

public:

long SecondDerivedInt()

{

return iii;

}

};

//Begin Of Second Test version

int main(array<System::String ^> ^args)

{

DerivedClass DD;

SecondDerivedClass SD;

Console::WriteLine( DD.DerivedInt() );

Console::WriteLine( SD.SecondDerivedInt() );

return 0;

}