Next: Interfacing with C++ at the Class Level, Previous: A Simple Example, Up: Building Mixed Ada & C++ Programs
In order to interface with C++ constructors GNAT provides the
pragma CPP_Constructor
(See Interfacing to C++, for additional information).
In this section we present some common uses of C++ constructors
in mixed-languages programs in GNAT.
Let us assume that we need to interface with the following C++ class:
class Root { public: int a_value; int b_value; virtual int Get_Value (); Root(); // Default constructor Root(int v); // 1st non-default constructor Root(int v, int w); // 2nd non-default constructor };
For this purpose we can write the following package spec (further information on how to build this spec is available in Interfacing with C++ at the Class Level and Generating Ada Bindings for C and C++ headers).
with Interfaces.C; use Interfaces.C; package Pkg_Root is type Root is tagged limited record A_Value : int; B_Value : int; end record; pragma Import (CPP, Root); function Get_Value (Obj : Root) return int; pragma Import (CPP, Get_Value); function Constructor return Root; pragma Cpp_Constructor (Constructor, "_ZN4RootC1Ev"); function Constructor (v : Integer) return Root; pragma Cpp_Constructor (Constructor, "_ZN4RootC1Ei"); function Constructor (v, w : Integer) return Root; pragma Cpp_Constructor (Constructor, "_ZN4RootC1Eii"); end Pkg_Root;
On the Ada side the constructor is represented by a function (whose name is arbitrary) that returns the classwide type corresponding to the imported C++ class. Although the constructor is described as a function, it is typically a procedure with an extra implicit argument (the object being initialized) at the implementation level. GNAT issues the appropriate call, whatever it is, to get the object properly initialized.
Constructors can only appear in the following contexts:
In a declaration of an object whose type is a class imported from C++, either the default C++ constructor is implicitly called by GNAT, or else the required C++ constructor must be explicitly called in the expression that initializes the object. For example:
Obj1 : Root; Obj2 : Root := Constructor; Obj3 : Root := Constructor (v => 10); Obj4 : Root := Constructor (30, 40);
The first two declarations are equivalent: in both cases the default C++
constructor is invoked (in the former case the call to the constructor is
implicit, and in the latter case the call is explicit in the object
declaration). Obj3
is initialized by the C++ non-default constructor
that takes an integer argument, and Obj4
is initialized by the
non-default C++ constructor that takes two integers.
Let us derive the imported C++ class in the Ada side. For example:
type DT is new Root with record C_Value : Natural := 2009; end record;
In this case the components DT inherited from the C++ side must be initialized by a C++ constructor, and the additional Ada components of type DT are initialized by GNAT. The initialization of such an object is done either by default, or by means of a function returning an aggregate of type DT, or by means of an extension aggregate.
Obj5 : DT; Obj6 : DT := Function_Returning_DT (50); Obj7 : DT := (Constructor (30,40) with C_Value => 50);
The declaration of Obj5
invokes the default constructors: the
C++ default constructor of the parent type takes care of the initialization
of the components inherited from Root, and GNAT takes care of the default
initialization of the additional Ada components of type DT (that is,
C_Value
is initialized to value 2009). The order of invocation of
the constructors is consistent with the order of elaboration required by
Ada and C++. That is, the constructor of the parent type is always called
before the constructor of the derived type.
Let us now consider a record that has components whose type is imported from C++. For example:
type Rec1 is limited record Data1 : Root := Constructor (10); Value : Natural := 1000; end record; type Rec2 (D : Integer := 20) is limited record Rec : Rec1; Data2 : Root := Constructor (D, 30); end record;
The initialization of an object of type Rec2
will call the
non-default C++ constructors specified for the imported components.
For example:
Obj8 : Rec2 (40);
Using Ada 2005 we can use limited aggregates to initialize an object invoking C++ constructors that differ from those specified in the type declarations. For example:
Obj9 : Rec2 := (Rec => (Data1 => Constructor (15, 16), others => <>), others => <>);
The above declaration uses an Ada 2005 limited aggregate to
initialize Obj9
, and the C++ constructor that has two integer
arguments is invoked to initialize the Data1
component instead
of the constructor specified in the declaration of type Rec1
. In
Ada 2005 the box in the aggregate indicates that unspecified components
are initialized using the expression (if any) available in the component
declaration. That is, in this case discriminant D
is initialized
to value 20
, Value
is initialized to value 1000, and the
non-default C++ constructor that handles two integers takes care of
initializing component Data2
with values 20,30
.
In Ada 2005 we can use the extended return statement to build the Ada equivalent to C++ non-default constructors. For example:
function Constructor (V : Integer) return Rec2 is begin return Obj : Rec2 := (Rec => (Data1 => Constructor (V, 20), others => <>), others => <>) do -- Further actions required for construction of -- objects of type Rec2 ... end record; end Constructor;
In this example the extended return statement construct is used to build in place the returned object whose components are initialized by means of a limited aggregate. Any further action associated with the constructor can be placed inside the construct.