EE-112: Class Implementation in Analog C++ PDF

a
Engineer To Engineer Note
EE-112
Technical Notes on using Analog Devices’ DSP components and development tools
Phone: (800) ANALOG-D, FAX: (781) 461-3010, EMAIL: [email protected], FTP: ftp.analog.com, WEB: www.analog.com/dsp
Class Implementation in Analog C++
Contributed by Graham Andrews,
C/C++ Compiler Developer,
DSP Development Tools Product Line
Overview
A "class" is a type in the C++ programming
language. The most simple form of this type is
likened directly to a struct type in C. In fact, the
keyword "class" can be replaced with the
keyword "struct" with no effect on the layout of
the internal representation of the object. The
difference is that C++ can place restrictions on
the accessibility of its class members. A struct
that is valid in C is valid in C++, and the same
rules of layout and parameter passing exist in
both C and C++. Member functions declared
within a class have no effect on the layout of a
given class object unless the member function is
declared virtual. When inheritance, virtual base
classes, and virtual functions are present, the
layout of C++ classes is more complicated.
A C struct is a valid C++ construct with the
same object layout:
struct A{
int x;
double y;
};
If the keyword "struct" is replaced with "class"
in C++, then the members x and y are implicitly
defined as private . Access specifiers define the
rules for using these members with the '->' and
'.' operators. The keyword "struct" makes these
members implicitly public in C++.
In C++, the previous example could be equally
written as:
class A{
public:
// public access
int x;
double y;
};
All the examples described in this note show
equivalent layouts in C++ and C terms. The
examples demonstrate how an object
representation in C++ translates to C, so there is
a direct correspondence to the generated code.
Inheritance
A class can be derived from one or more
classes. The classes from which it is derived are
called base classes. An object of a derived class
inherits all the members of the base classes. A
derived class can have base classes, which
themselves are derived, and each class may have
more than one base class. The layout of class
objects follows a simple pattern. This pattern is
illustrated by the following examples, which
show the underlying class layout in terms of C.
Example 1. Base & Derived Classes
class A{
int a1;
double a2;
};
class B:A{
int b1;
int b2;
};
C layout equivalents:
struct A{
int a1;
double a2;
};
struct B{
struct A base_A;
int b1;
int b2;
};
Copyright 2000, Analog Devices, Inc. All rights reserved. Analog Devices assumes no responsibility for customer product design or the use or application of customers’
products or for any infringements of patents or rights of others which may result from Analog Devices assistance. All trademarks and logos are property of their respective
holders. Information furnished by Analog Devices Applications and Development Tools Engineers is believed to be accurate and reliable, however no responsibility is
assumed by Analog Devices regarding the technical accuracy of the content provided in all Analog Devices’ Engineer-to-Engineer Notes.
Example 2. Inheritance Using Classes A & B
from Example 1
class C{
char c1[10];
};
class D : B{
float d1;
short d2;
};
class object layout to point to the instance of the
base.
In Example 2, class B has class A as a base class,
and class C has class A as a base class. If class D
has both B and C as base classes, then there
would be two class A parts of class D. However,
if class A is declared to be a virtual base of class
B and of class C, then only one A sub-object is
present in a D object.
class E: C ,D {
unsigned int e1:2;
unsigned int e2:6;
unsigned e3:17;
};
Example 3. Virtual Base Classes
C layout equivalents:
class A: Z{
int a1;
};
struct A {
int a1;
double a2;
};
struct B {
struct A __b_A;
int b1;
int b2;
};
struct C {
char c1[10];
};
class Z{
int z1;
int z2;
};
class B : virtual A{
int b1;
};
class C : virtual A{
int c1;
};
class D: B, C{
int d1;
};
struct D {
struct B __b_B;
float d1;
short d2;
};
C layout equivalents:
struct E {
struct C
struct D
unsigned
unsigned
unsigned
};
struct A {
struct Z __b_Z;
int a1;
};
base_C;
base_D;
int e1: 2;
int e2: 6;
int e3: 17;
Virtual Base Classes
Every class in a hierarchy that specifies a base
class to be virtual, shares a single object of that
base class within a derived object. In terms of
the layout, the compiler adds pointers to the
struct Z {
int z1;
int z2;
};
struct B {
int b1;
struct A *__p_A;
/* pointer to A part of a B
object */
struct A __v_A;
/* instance of A part in B
object */
};
EN-112
Page 2
Technical Notes on using Analog Devices’ DSP components and development tools
Phone: (800) ANALOG-D, FAX: (781) 461-3010, EMAIL: [email protected], FTP: ftp.analog.com,
WEB: www.analog.com/dsp
struct __SO__B {
/* If a B is used as a base class,
this struct is used instead of
struct B (since the instance of
an A is determined by the
derived class).
*/
int b1;
struct A *_p_A;
/* pointer to A part within the
derived object */
};
struct C {
int c1;
struct A *__p_A;
/* pointer to A part of a C
object */
struct A __v_A;
/* instance of A part in C
object */
};
struct __SO__C {
/* If a C is used as a base class,
this struct is used instead of
struct C (since the instance is
determined by the derived class).
*/
int c1;
struct A *__p_A;
/* pointer to A part within the
derived object */
};
struct D {
struct __SO__B __b_B;
struct __SO__C __b_C;
/* For Object D, note that
base_B.__p_A and base_C.__p_A
point at the instance of
__v_A within the struct D
declaration.
*/
int d1;
struct A __v_A;
};
Member functions
Member functions are functions that are
declared within the scope of a class declaration,
and their bodies maybe defined within or
outside this declaration. Member functions that
are not declared static have a hidden first
parameter called the 'this' parameter. The 'this'
parameter is declared as a constant pointer to the
class for which it is declared.
Example 4. Non-Static Member Functions
class X{
int a;
int func(int);
};
X obj;
obj.func(5);
This example translates in C terms as follows:
struct X{
int a;
};
int X_func(struct X*, int);
struct X obj;
func(&obj, 5);
A static member function is an externally visible
function that does not have a hidden 'this'
pointer. This function is general for all objects
of this class and is able to access static data
members (in most cases). Static data members
are data members of a class declared with the
'static' storage class specifier. These members
do not form part of the class object; they may be
used for counting objects of a class. Like any
other external variable in a C++ program, static
data members are externally visible objects and
are defined once.
Example 5. Static Member Functions
class X{
int a;
static int count;
static void counter();
};
X obj;
int X::count = 0;
/* define and initialize static
data */
EN-112
Page 3
Technical Notes on using Analog Devices’ DSP components and development tools
Phone: (800) ANALOG-D, FAX: (781) 461-3010, EMAIL: [email protected], FTP: ftp.analog.com,
WEB: www.analog.com/dsp
X::counter();
obj.counter();
/* same as line above */
This example translates to C equivalent:
struct X{
int a;
};
struct X obj;
int X_count = 0;
void X_counter();
X_counter();
X_counter();
Virtual functions
A virtual function does have an effect on the
object layout. A virtual function is a non-static
member function that is declared with the
specifier keyword 'virtual' . The calling
mechanism allows invocation of a member
function declared in a derived class even if its
access is denied through a pointer or a reference
to a base class. The mechanism works as an
additional pointer placed in the object to a
virtual table. The virtual table contains the
address of the function to be invoked and an
associated index to modify the object pointer to
point to the whole-derived object.
class B{
public:
void func(int);
virtual void func(double);
};
class D: public B{
public:
void func(int);
void func(double);
/* this func is a virtual
function inherited from
class B */
};
B b_obj;
D d_obj;
B *p_b2 = &d_obj;
/* points to the B part of a D
object d_obj */
p_b1->func(5);
/* calls B::func(int) as
B_func(p_b1,5); not virtual */
p_b1->func(2.0)
/* calls B::func(double).
Since it is a virtual function
call, the compiler has to index
the object pointed at by
p->b1 and to look up the virtual
table. This consists of a
modifying offset for the 'this'
parameter to point to the
correct part of the object along
with the address of the
appropriate function:
*(p->b1+tab[offset]_vfaddr)
(p->b1+tab[offset]_thismod,2.0);
*/
p->b2->func(5);
/* calls B::func(int) as
B_func(p_b2,5); not virtual */
p->b2->func(2.0)
/* calls D::func(double).
This works using the same
methodology described for
p_b1->func(2.0), but the table
placed in the B part is
different as it was created as
part of a D object.
*/
Example 6. Virtual Functions
class B1{
public:
int b1;
void func(int);
virtual void func(double);
};
class B2{
public:
int b2;
virtual void func(char *);
};
B *p_b1 = &b_obj;
EN-112
Page 4
Technical Notes on using Analog Devices’ DSP components and development tools
Phone: (800) ANALOG-D, FAX: (781) 461-3010, EMAIL: [email protected], FTP: ftp.analog.com,
WEB: www.analog.com/dsp
class D: public B1, public B2{
public:
int d1;
void func(int);
void func(double);
/* this func is a virtual
function inherited from
class B1 */
void func(char *);
/* this func is a virtual
function inherited from
class B2 */
};
B1 b1_obj;
B2 b2_obj;
D d_obj;
B1 *p_b1 = &b1_obj;
B1 *p_b1d = &d_obj;
/* points to the B1 part of a D
object d_obj */
B2 *p_b2 = &b2_obj;
B2 *p_b2d = &d_obj;
/* points to the B2 part of a D
object d_obj */
void
void
void
void
B1::func(double){};
D::func(double){};
B2::func(char *){};
D::func(char *){};
The generated code in terms of C:
struct _VTABLE {
short mod;
/* modifies the this parameter
by adding an offset */
short unused;
/* unused hangover from cfront
and compatibility */
void (*f)();
/* address of virtual function
to be called */
};
struct B1 {
int b1;
struct _VTABLE *__vptr;
};
struct B2 {
int b2;
struct _VTABLE *__vptr;
};
struct D {
struct B1 __b_B1;
struct B2 __b_B2;
int d1;
};
extern void func__2B1Fd(struct B1
*const, double);
static struct B1 *__ct__2B1Fv
(struct B1 *const);
extern void func__2B2FPc(struct B2
*const, char *);
static struct B2 *__ct__2B2Fv
(struct B2 *const);
extern void func__1DFd(struct D
*const, double);
extern void func__1DFPc(struct D
*const, char *);
struct _VTABLE __vtbl__2B1[2] =
{{((short)0),((short)0),((void
(*)())0)},
{((short)0),((short)0),((void
(*)())func__2B1Fd)}};
struct _VTABLE __vtbl__2B2[2] =
{{((short)0),((short)0),((void
(*)())0)},
{((short)0),((short)0),((void
(*)())func__2B2FPc)}};
struct _VTABLE __vtbl__1D[3] =
{{((short)0),((short)0),((void(*)()
)0)},
{((short)0),((short)0),((void(*)())
func__1DFd)},
{((short)0),((short)0),((void
(*)())func__1DFPc)}};
struct _VTABLE __vtbl__2B2__1D[2] =
{{((short)0),((short)0),((void
(*)())0)},
{((short)(-8)),((short)0),((void
(*)())func__1DFPc)}};
EN-112
Page 5
Technical Notes on using Analog Devices’ DSP components and development tools
Phone: (800) ANALOG-D, FAX: (781) 461-3010, EMAIL: [email protected], FTP: ftp.analog.com,
WEB: www.analog.com/dsp
In the previous example, an object of type B1
has its _vptr member initialized to the address
of __vtbl__2B1[0]. An object of type B2 has
its __vptr initialized to &__vtbl_2B2[0]. An
object of type D is more complicated and has a
virtual table for its B1 and B2 parts. An object
of type D requires a virtual table pointer for
itself as a D, for its B1 and B2 parts. In fact, D
shares the virtual table with B1 by pointing its
__vptr to &__vtbl__1D[0], and it sets the
__vptr
for
its
B2
part
to
&__vtbl__2B2__1D[0].
EN-112
Page 6
Technical Notes on using Analog Devices’ DSP components and development tools
Phone: (800) ANALOG-D, FAX: (781) 461-3010, EMAIL: [email protected], FTP: ftp.analog.com,
WEB: www.analog.com/dsp