I have been asked to request a clarification on the sequencing of creating derived class data members versus calling the base class constructors. There is also some uncertainty about what implementation of a virtual function gets called in a constructor, if the derived class object has not been fully initialized at that point. Here is some of the discussion around it here: QUOTE 1: If derived class data members are not created until AFTER the base class constructors are called, this is too late in some cases. It is typical (at least in C++) to have virtual methods which get called from the base class constructor, but those virtual methods in the derived class may reference derived class data object. I have run into this in a couple of cases and the symptoms can be very strange (from a null pointer reference when calling a queue method to an int getting a value of 0 instead of the value assigned by the class). Sometimes this can be worked around, but the work around is always tedious. The bigger problem is that the symptoms can be very strange and so it is very hard to even find when you have this problem. Below is a very simple testcase which demonstrates the problem. In the example, the base class object calls the virtual function initialize(). When a derived class object is instantiated, its initialize() function is exectuted. This function zeros out the queue... since the queue doesn't exist yet I get a null pointer reference on the call to size(). The order that c++ does things is: allocate memory for the entire class structure initialize all objects in the derived class execute the constructors from the base class up to the derived class I think that systemverilog is supposed to do the same thing. But, it appears that sv is doing the allocation and initialization in a bottom up manner while it executes the constructors which is not correct. module test; class base; virtual function void initialize(); return; endfunction function new(); initialize(); endfunction endclass class der extends base; protected int queue[$]; function new(); super.new(); endfunction virtual function void initialize(); init_queue(); endfunction function void init_queue(); for(i=0; i<queue.size(); ++i) queue.push_back(0); endfunction endclass der foo = new; endmodule QUOTE 2: Here is what I think should happen in a class constructor, and why 1. Call super.new() if this is a derived class. SV3.1a Section 11.16 is very clear to me: the first action a constructor takes it to call the base class constructor. If the base class constructor requires arguments, these can be specified at the "top" of the class declaration (just before the semi-colon that appears after the keyword class) - in this case there is, IMO, no visiblity over class items in the derived class, so the issue raised does not apply. You can also specfiy arguments to a base class constructor by an explicit call to 'super.new (...);' in a user- defined constructor - in this case there is an issue as to what can be used in this argument list. It is my expectation that only arguments to the user-defined constructor be allowed in the super.new(...) call. Attempts to access non-static class properties or methods is not allowed in this context precisely because they have not yet been initialized. 2. All automatic class properties declared in this class are initialized to their default (X, e.g.) value. 3. User-supplied initialization expressions for automatic class properties are evaluated and assigned. 4. The body of the (implicit or user-defined) constructor, minus the explicit super.new(...) if it exists, is executed. Implicit constructors just return. User-defined constructors are allowed to do anything an automatic non-virtual method is allowed to do. However, calling virtual methods from a class constructor is a bad idea (and should probably be disallowed) because the state of the vtable and the values of derived class properties is undefined at this point! I believe this approach to constructor behavior is consistent with the LRM (although that's not saying much because the LRM is so vague). It is also consistent with the way C++ classes work. It is also intuitively appealing to me to allow initialization to flow from base classes to derived classes. QUOTE 3: Upon further inspection of your example, I see you are calling initialize within the new function. In C++, calling a virtual method within new will call that class' method, not th e derived method. From the C++ LRM ISO-14882-2003, section 12.7: "When a virtual function is called directly or indirectly from a constructor ...the function called is the one defined in the constructor's ... own class or in one of its bases, but not a function overriding it in a class derived from th e constructor..." Perhaps the SV behavior should be the same: : "turn off" the virtual mechanism when executing the new method. This may lead to undesirable behavior, but at least the method call won't end up referring to non-existent/uninitialized variables, and it'll be aligned with what C++ does. QUOTE 4: I learned C++ at a time when calling virtual functions virtually from a class co nstructor was understood to be asking for trouble - you could only get useful/pr edictable results if you were very careful about what members the virtual functi on accessed when called as part of construction. It looks like the C++ language definition has tried to address this by having the vtable change as the constru ctor execution context changes. QUOTE 5: Adam is correct about how c++ works, and the c++ behavior has the effect of doing precisely what I would want to do in this type of case. If you can disable the vtable lookup during construction then that would solve my issues for sure (and would be consistent with c++. One note is that if you have a pure virtual function then it is not allowed to be called in the constructor (this makes sense since there is no implementation). Here is a very simple example in c++ and sv. You can see that the issue with sv is that the derived class version is executed before the object is created. //C++ Version #include <iostream> using namespace std; struct base { base() { cout << "base::base()" << endl; initialize(); } virtual void initialize() { cout << "base::initialize()" << endl; } }; struct derived : public base { derived() {cout << "derived::derived()" << endl; initialize(); } virtual void initialize() { base::initialize(); cout << "derived::initialize()" << endl; } }; int main() { derived h; return 0; } $ ./test base::base() base::initialize() <<--- from base ctor, only base initialize is called derived::derived() base::initialize() derived::initialize() // SystemVerilog Version module top; class base; function new; $display("base::base()"); initialize(); endfunction virtual function void initialize(); $display("base::initialize()"); endfunction endclass class derived extends base; function new; $display("derived::derived()"); initialize(); endfunction virtual function void initialize(); super.initialize(); $display("derived::initialize()"); endfunction endclass derived h = new; endmodule Here is the result from your proposed behavior: base::base() base::initialize() derived::initialize() <<<--- THIS IS THE PROBLEM derived::derived() base::initialize() derived::initialize() ENDQUOTE Steven Sharp sharp@cadence.com -- This message has been scanned for viruses and dangerous content by MailScanner, and is believed to be clean.Received on Mon, 23 Apr 2007 17:30:53 -0400 (EDT)
This archive was generated by hypermail 2.1.8 : Mon Apr 23 2007 - 14:31:55 PDT