Deprecated: Non-static method StringParser_Node::destroyNode() should not be called statically, assuming $this from incompatible context in /www/htdocs/w008ab83/ad/stringparser_bbcode/src/stringparser.class.php on line 356
AUDACIA Software - Why Delphi classes must be created on the heap
AUDACIA Software

Why Delphi classes must be created on the heap

A short outline discussing the technical differences between Delphi and C++ classes.
Moritz Beutel, October 14th, 2008, July 6th, 2009



  1. TObject
  2. Construction and destruction
  3. Virtual functions in constructor and destructor
  4. Exceptions in the Constructor
  5. Initialization
  6. Multiple inheritance
  7. Virtual constructors
  8. Class functions
  9. Freeing
  10. Semantics
  11. Usage
  12. Temporary stack object syntax
  13. Allocation und Deallocation
  14. RTTI
  15. Implicitly generated methods
  16. Comments



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:
void foo (void)
{
    TStringList sl;
    ...
}

[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:


TObject


All classes derived from TObject are considered Delphi-style classes.

Construction and destruction


Delphi 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 destructor


When 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):
struct Base
{
    virtual void foo (void) = 0;
    Base (void) { foo (); }
};
struct Derived : Base
{
    virtual void foo (void) { ShowMessage ("It's me!"); }
};

struct TBase : TObject
{
    virtual void foo (void) = 0;
    TBase (void) { foo (); }
};
struct TDerived : TBase
{
    virtual void foo (void) { ShowMessage ("It's me!"); }
};

void bar (void)
{
    TDerived* td = new TDerived; // displays "It's me!"
    Derived d; // displays "Pure virtual function called"
}

The same difference applies for destructors.


Exceptions in the Constructor


When 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.


Initialization


All 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 inheritance


The VMT layout follows the Delphi rules, and does therefore not permit deriving from multiple base classes. Deriving from multiple interfaces is possible though.


Virtual constructors


Constructors can be virtual. (There is no syntax for invoking virtual constructors directly though; this needs to be done from Delphi code.)


Class functions


Delphi 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.)


Freeing


For 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.


Semantics


Delphi code like
procedure foo;
var
  sl1, sl2: TStringList;
begin
  sl1 := TStringList.Create;
  sl2 := sl1; // this line
  ...
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.


Usage


When 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:
void foobar (void)
{
    TStringList* sl = 0;
    try
    {
        sl = new TStringList;
        ...
    }
    __finally
    {
        delete sl;
    }
}


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:
#include <memory>

void foobar (void)
{
    std::auto_ptr <TStringList> sl (new TStringList);
    ...
}


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:
#include <memory> // TR1 support since C++Builder 2009
#include <vector>

typedef std::tr1::shared_ptr <TStringList> TStringListPtr;

TStringListPtr stringListFactory (void)
{ return TStringListPtr (new TStringList); }

void foobaz (void)
{
    std::vector <TStringListPtr> slv;
    slv.push_back (stringListFactory ());
    ...
}



Addenda as of 06.07.2009:

Temporary stack object syntax


As 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:
class TMyThread : public TThread
{
protected:
    virtual void __fastcall Execute (void) {}

public:
    TMyThread (void) : TThread (false) {}
};

void startMyThreadTheFreakyWay (void)
{
    TMyThread ().FreeOnTerminate = true;
}
However, as with throwing exceptions, the TMyThread object is not really constructed on the stack. The generated code is equivalent to this:
void startMyThreadTheNormalWay (void)
{
    new TMyThread ()->FreeOnTerminate = true;
}
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 Deallocation


The 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):
#include <string>

void constructSomeObjects (void)
{
        // allocation on the stack
    std::string myStackString ("foo");
    myStackString += std::string ("bar");

        // allocation on the heap
    std::string* myHeapString = new std::string ("baz");
    delete myHeapString;

        // explicit construction and destruction
    char buffer[sizeof (std::string)];
    std::string* anotherStackString = new (buffer) std::string ("wtf?");
    anotherStackString->~string ();
}
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:
class MyCppClass
{
public:
    void* operator new (unsigned long size)
    { return std::malloc (size); }
    void operator delete (void* ptr)
    { std::free (ptr); }

    virtual ~MyCppClass (void) {}
};
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():
class TMyCustomAllocatedClass : public TObject
{
public:
#ifdef __CODEGEARC__ // C++Builder 2009 supports class methods
    __classmethod virtual TObject* __fastcall NewInstance (void)
    {
        void* memory = std::malloc (InstanceSize ());
        return InitInstance (memory);
    }
#else
    virtual TObject* __fastcall NewInstance (TMetaClass* Self)
    {
        void* memory = std::malloc (Self->InstanceSize ());
        return Self->InitInstance (memory);
    }
#endif
    virtual void __fastcall FreeInstance (void)
    { std::free (this); }
};

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":
  // System.pas, 263ff
  { Hidden TObject field info }
  hfFieldSize          = 4;
  hfMonitorOffset      = 0;

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.


RTTI


The 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 methods


According 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:
class ClassWithProperties
{
private:
    int FFoo;
    String FBar;

public:
    __property int Foo = { read = FFoo, write = FFoo };
};
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.


Comments



Deprecated: 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

New entry:

Name:
E-Mail:
Website:
Date:
Number of characters in your name:
Message: