|
Why Delphi classes must be created on the heapA short outline discussing the technical differences between Delphi and C++ classes.
C++ programmers who are new to Delphi or C++Builder tend to find it strange that Delphi-style classes cannot be constructed on the stack in C++Builder. The attempt to do this raises a compiler error:
[BCC32 Error] main_unit.cpp(16): E2459 VCL style classes must be constructed using operator new This is one of the situations where the less obvious dissimilarities of C++ and Delphi-style classes cause unanticipated problems. In this article, I'll try to give an overview of the fundamental differences between C++ and Delphi classes: TObjectAll classes derived from TObject are considered Delphi-style classes. Construction and destructionDelphi classes have different behavior during construction and destruction than C++ classes. The constructors are called in reverse order, top-down. Every class constructor must explicitly call its base class constructor. (For Delphi classes implemented in C++, the compiler enforces a call to the base class constructor in the initialization list.) Virtual functions in constructor and destructorWhen calling virtual functions which are overridden in a derived class from inside a base class constructor, the overridden function is called. For C++ classes, such a call invokes the base classes' virtual function implementation (or, if that does not exist, a "Pure virtual function called" assertion):
The same difference applies for destructors. Exceptions in the ConstructorWhen an exception is thrown in a constructor, the destructor is called, and all member objects that have already been constructed are destroyed. In a C++ class, the destructor is not called. InitializationAll data fields of a newly allocated, unconstructed Delphi class are initialized with zero. This is important to know if you call virtual functions from inside a constructor because the derived class has not been constructed at the time of the virtual function's execution. Multiple inheritanceThe VMT layout follows the Delphi rules, and does therefore not permit deriving from multiple base classes. Deriving from multiple interfaces is possible though. Virtual constructorsConstructors can be virtual. (There is no syntax for invoking virtual constructors directly though; this needs to be done from Delphi code.) Class functionsDelphi classes support both static functions (known as "static class functions") and virtual static functions (known as "virtual class functions"). In C++Builder 2009, virtual static functions can be declared and overridden in Delphi-style classes using the new __classmethod keyword. (There are however some limitations on the usage of metaclass polymorphism, e.g., it is not yet possible to call a virtual destructor via a metaclass pointer in C++. This can be worked around by writing a small wrapper function in Delphi.) FreeingFor Delphi classes, there is one well-defined way to destroy an object: by calling its Free() method. This method takes care of deconstructing the object and freeing the memory using the heap memory manager. All Delphi code handles Delphi-style objects this way, and if you delete a Delphi class using the C++ "delete" keyword, the compiler generates an inline equivalent of TObject::Free(). In contrast, a C++ class can be located on the heap, on the stack or in static memory. The standard requires that the destructor frees the object memory for heap-allocated objects, therefore a destructor must know whether the object was allocated on the heap or on the stack. This is usually implemented by passing a "hidden parameter" to the destructor which depends on whether the object has been deleted (delete obj) or explicitly (obj->~T ()) or implicitly destroyed (when the scope is left). The destructors of Delphi classes have an additional parameter as well, but there is no direct way to access it. SemanticsDelphi code like copies the object reference, not the object itself. Delphi does not provide intrinsic functionality for copying objects like C++ does with the implicitly defined operator = and the copy constructor. (TPersistent does however introduce the Assign() method.) Delphi classes don't behave like value-type objects - they aren't meant to! -, and therefore, it would not be possible to use them as return values or in STL containers even if stack and static allocation were allowed.This is not necessarily a disadvantage. As Barry Kelly discusses here, the C++ approach of "making value types the default" does not fit for most kinds of custom classes. Furthermore, to return value types or to resize vector containers without notable performance decrease, a C++ programmer must rely on things like Named Return Value Optimization (an optimization that is not as widespread as it should) and Move Semantics (part of the next C++ standard proposal, already implemented in C++Builder 2009). A Delphi programmer would just return a pointer to the newly constructed object, thereby passing the object ownership to the caller. UsageWhen constructing a Delphi class object locally, you should care about exception safety. Manually invoking delete is not exception-safe; you must at least wrap the code into a try/finally construct, as you would do in Delphi:
While this is quite straightforward, it looks rather ugly, and it becomes even more ugly if multiple objects are constructed at different times or in sub-scopes. The C++ approach would be to use a RAII wrapper such as std::auto_ptr:
However, you cannot use auto_ptr as a return value or in containers as they do not provide copy semantics. Use a reference-counting pointer like std::tr1::shared_ptr instead:
Addenda as of 06.07.2009: Temporary stack object syntaxAs explained in External Exception EEFFACE, the compiler permits the construction of a Delphi-style object without new if the syntax for constructing temporary stack objects is used. Although the reason for this is supposed to be a paradigmatic concession to the C++ way of throwing exceptions, the compiler does not limit this syntax to the throw expression. Thus, the following code is valid: However, as with throwing exceptions, the TMyThread object is not really constructed on the stack. The generated code is equivalent to this: It is not known whether this "semantic leak" is to be considered a bug or a feature. I recommend to not use it except when throwing an exception - it is easy to break, and it usually leaves a memory leak because what looks like a stack construction is actually a heap construction.Allocation und DeallocationThe creation phase of a C++-style object is strictly separated into allocation and construction. The caller of constructor and destructor is responsible for allocation and deallocation (with one exception as denoted below): To ease the customization of the allocation behavior, the language further allows to overload the operators for memory allocation and deallocation (operator new and operator delete), either globally or per class: If the destructor is virtual, the invocation of the deallocation function is performed by the destructor:The construction phase for Delphi-style classes has been described here by Hallvard Vassbotn. The most relevant difference is that allocation and deallocation are performed by constructors and destructor. The allocation function, NewInstance(), is a virtual class function; thus, to emulate the allocation behavior of MyCppClass, it is necessary to overwrite NewInstance() and FreeInstance():
InitInstance() initializes the given memory with zeroes and sets the VMT pointer. To know how much memory to allocate, InstanceSize() must be called; it reads the allocation size from the object's RTTI. Note that sizeof(TMyCustomAllocatedClass) is not an adequate replacement for two reasons: first, our allocation function will be called for derived classes as well, and secondly, in Delphi/C++Builder 2009, CodeGear introduced so-called "hidden fields":
Every Delphi-style object contains not only the normal class layout but also additional four bytes; these bytes can be used to lock an object during a critical section using the methods of System.TMonitor (which resemble the methods of System.Threading.Monitor in .NET). InstanceSize() adds hfFieldSize to the class layout size, which sizeof(TMyCustomAllocatedClass) does not. The most obvious consequence of this allocation concept is that overloading the global operator new cannot affect Delphi-style classes as they always have a virtual destructor and therefore always use FreeInstance() to deallocate. For the same reason, it is not possible to allocate a Delphi-style object on the stack. This does not mean that it is not possible technically. The constructors and destructors of Delphi-style classes have hidden parameters as well; they are used to prevent multiple allocations or deallocations when invoking base class constructor/destructor or when forwarding a constructor. With some tricks, it is actually possible to allocate a Delphi-style object on the stack - but the intricate trickery prevents it from being particularly useful. RTTIThe compiler does not (only) generate C++-style, but Delphi-style RTTI for Delphi classes, which makes published sections possible. Methods, fields and properties declared in a published section are represented in the RTTI of the class. Implicitly generated methodsAccording to the C++ language specification, the compiler generates default constructor, copy constructor, assignment operator and destructor for classes and structures unless they are provided explicitly. This applies to Delphi-style classes as well, unless a class has both properties and non--trivial member types:
E2328: Classes with properties cannot be copied by value (The compiler inconsequently permits to copy-construct such a class though.) References[1] The C++Builder 6 documentation, especially the chapter Programming with C++Builder / C++ language support for VCL and CLX [2] Barry Kelly, Programming language design philosophy: C++'s value orientation, 08.08.2006 [3] Ayman B. Shoukry, Named Return Value Optimization in Visual C++ 2005, 10.2005 [4] The C++ Standards Committee, A Brief Introduction to Rvalue References, 12.06.2006 [5] Hallvard Vassbotn, The Rise and Fall of TObject, 07.1998 This article is loosely based on information I previously posted in the c++.de board. CommentsDeprecated: mysql_connect(): The mysql extension is deprecated and will be removed in the future: use mysqli or PDO instead in /www/htdocs/w008ab83/ad/phputils/dbc_mysql.php on line 112 |