CAA V5 C++ Coding Rules

Download as pdf or txt
Download as pdf or txt
You are on page 1of 19
At a glance
Powered by AI
The document outlines rules and best practices for writing C++ code, dealing with object lifecycles, and using the CAA V5 Object Modeler.

Each C++ or C entity should have its own header file with the same name. Header file contents should be enclosed in preprocessor directives to prevent multiple inclusions.

Only include header files if the class, enum, macro, typedef or #define is actually used. Avoid including unnecessary files like stream.h.

4/17/2019 CAA V5 C++ Coding Rules

Rules and Standards CAA V5 C++ Coding Rules

Technical Article Rules, hints and tips to write C++ code

Abstract

This article gives you a set of rules and advice to better write your C++ code, to correctly deal with object
lifecycle, and to appropriately use the CAA V5 Object Modeler.

C++ Rules
Lifecycle Rules
Object Modeler Rules

C++ Rules

This set of rules deal with the C++ entities you will use.

Create a Header File for each C++ or C Entity

Create a separate header file for each class, interface, structure, global enumumeration, global function, and
macro, and put in this file only the declaration of this entity. This file must have the same name than the entity.
For example, the CATBaseUnknown class header file is CATBaseUnknown.h.

[Checklist | Top]

Use Preprocessor Directives to Enclose Your Header File Contents

This is the appropriate means to protect code from a multiple inclusion of your header file. Do this as follows, for
example for the CATBaseUnknown class:
#ifndef CATBaseUnknown_h
#define CATBaseUnknown_h

... // Put here the #include statements,


... // forward class declarations, and the class stuff

#endif

[Checklist | Top]

Use #include Judiciously

When you create a header file, always ask you the following question for each file you include: "Do I really need to
include this file, or is a forward declaration enough?" Here is the answer to this question:

Use #include to include the header file of the


base class

Use #include to include the header file of a class


when an instance of this class is used as a data
member

https://www.maruf.ca/files/caadoc/CAADocTechArticles/CAADocCppCodingRules.htm 1/19
4/17/2019 CAA V5 C++ Coding Rules

Use the class forward declaration when a reference


of, a value, or a pointer to a class is used as a
method returned value or parameter, or if a pointer
to a class is used as a data member

For any included file, check that you actually use the class, the enum, the macro, the type defined by a typedef,
or a parameter among the set defined by #define contained in the file, or otherwise remove it.

Do not include C++ header files, such as stream.h or iostream.h, if they are useless, since they can include static
data that is in any case allocated whatever the way you use these files.

Never copy and paste sets of #include statements from another file. This is the worst you can do, since it's then
more difficult to sort useful and useless files. If you include useless header files, your code grows and the time
required to manage the module dependency impacts and to build it increases.

[Checklist | Top]

Do Not Use namespace Statements

Comply to the naming rules [1] instead.

[Checklist | Top]

Do Not Use Threads

Do not use threads.

[Checklist | Top]

Do Not Use Templates

Do not use Templates.

They are not portable to different operating systems, especially in the way they are supported by compilers and
link-editors.

[Checklist | Top]

https://www.maruf.ca/files/caadoc/CAADocTechArticles/CAADocCppCodingRules.htm 2/19
4/17/2019 CAA V5 C++ Coding Rules

Do Not Use Multiple Inheritance

The main problem raised by multiple inheritance is the ambiguity on the multiple inherited members, whether they
come from two different base classes that feature members with the same name, or from the same base class that
is multi-inherited. Use instead the CAA V5 Object Modeler that offers other means, such as interfaces and
components, to deal with inheritance while keeping C++ single inheritance.

[Checklist | Top]

Do Not Use Virtual Inheritance

Virtual inheritance is used in conjunction with multiple inheritance to solve the


diamond ambiguity. This happens when a class inherits from two classes that
themselves inherit from the same class. Since multiple inheritance shouldn't be used,
virtual inheritance shouldn't be used too.

[Checklist | Top]

Use Only Public Inheritance

Inheritance can be set to public, protected, or private. The following table summarizes the status of the members
of the base class in the derived class, with respect to the inheritance mode.
Inheritance mode
Base class
public protected private
member status
public public protected private
protected protected protected private
private private private private

To make sure that base class public members remain public, and that the protected ones remain protected in the
derived class, always use public inheritance.

[Checklist | Top]

Do Not Implement friend Classes

You may do so if the two friend classes are conceptually one object, that is share the same life cycle. This occurs
when a 'big' object has to be split in two parts. Facing this situation, consider using aggregation as an alternative
technique.

[Checklist | Top]

Do not Expose Data Members as Public

If you do so, you give a direct access to your data members to any user of your class instances. This breaks
encapsulation. Set your data members as private and expose methods to access them.

[Checklist | Top]

Avoid Defining Static Data Members

https://www.maruf.ca/files/caadoc/CAADocTechArticles/CAADocCppCodingRules.htm 3/19
4/17/2019 CAA V5 C++ Coding Rules

Static is synonymous of memory fragmentation and pagination. In addition, a static member function is required
to handle a static data member. Before defining a static data member, make sure this data is really common to all
instances of your class, such as an instance counter, and not only to some of them.

[Checklist | Top]

For each Class, Provide by Default a Copy Constructor, a Default Constructor, a Destructor, and an
Assignment Operator

This will help your clients assume that these "basic" constructors always exist.

Warning: Don't do so, however, if this breaks the logic of your objects (e.g. some object absolutely requires some
other object referenced in its constructor: don't provide a default constructor for them, or better provide a default
constructor and an Init method to pass the initialization parameters).

For extensions: This rule is especially true for extension classes: remember, those classes are not
autonomous, since they are extensions of some other classes. As a consequence, their creation is not left to their
clients, because these clients never manipulate them directly. Therefore, providing a copy constructor and an
assignment operator for these classes is useless and increases code size. But if you don't provide them, the C++
compiler will do it for you. To prevent this, simply declare the default constructor and the assignment operator as
non virtual in the extension class private part, and do not provide their implementation. Thus, the C++ compiler
will not attempt to provide their default implementation and will not attempt to allocate room for them in the
virtual function table. The only thing to remember is to never call them in the extension class code.

[Checklist | Top]

Always Declare the Destructor as Virtual for Classes to Derive

This is important when an instance of a derived class is identified using a pointer to its base class. Assume the
following:
class A
{
public :
A();
~A();
...
};
class B : public A
{
public :
B();
~B();
...
}

Suppose that the client handles a pointer to a B instance using the A type:
...
B * pB = new B(); // Calls A(), and then calls B()
A * pA = pB;
...
delete pA; // Calls ~A() only
...

When this occurs, the destructor of A is called, since pA is a pointer to A, but the destructor of B is not called, and
since pA is a B object, only its A part is deleted, thus causing memory leaks. This is because the destructor of A is
not virtual. If this destructor were virtual, the destructor of B would be called first, and then the destructor of A as
shown below.
...
B * pB = new B(); // Calls A(), and then calls B()
A * pA = pB;
https://www.maruf.ca/files/caadoc/CAADocTechArticles/CAADocCppCodingRules.htm 4/19
4/17/2019 CAA V5 C++ Coding Rules
...
delete pA; // Calls ~B(), and then calls ~A()
...

And there is no memory leak!

[Checklist | Top]

Do Not Declare Virtual Methods within Class Private Parts

Since a virtual method is intended to be overridden in a derived class, it must be accessible from this derived
class. Inserting a virtual method in the private part of a class hides it from its derived classes, and from the client
applications that use the class as well, and thus prevents from overriding it.

[Checklist | Top]

Declare the Methods Intended to Be Redefined as Virtual

These methods are of course public or protected members of your class. For example,
class CATClass
{
public
...
virtual HRESULT ComputeUsingAGoodAlgorithm(); // Can be redefined in derived classes
HRESULT ComputeUsingMyAlgorithm(); // Cannot be redefined in derived classes
...
};

The methods declared as virtual can be redefined when a client application derives the class. This enables objects
to be polymorphically processed and methods to be adapted to specialized objects. Because you may not, in the
general case, predict who will ever derive your classes, and why, carefully set as virtual all the methods that make
your class a base class and that must be redefine in the derived class. By doing so, you respect the future, by
preserving the ability of possible future derivations to adapt your methods to the new objects.

Advice: Apply this rule unless you design a class not intended for derivation, because it would make your class
bigger in prevision of an event that will never occur.

[Checklist | Top]

Avoid Implementing inline Methods

inline methods are faster than classical methods because they do not branch to another part of the code, and
consequently do not deal with all the current data saving and restoring operations. The inline method
executable code is added at each call location by the compiler. (With a macro, the preprocessor adds source code.)
Even if it is faster, the general rule is to avoid inline methods, because any modification to such a method forces
the client application to rebuild.

Advice: You can use inline methods only if you need performance with a very small method. If you code large
inline methods, the inline advantage disappears. Performance analysis should be made prior to deciding
which method should be inline, and which should not.

You must never:

Create virtual inline methods

The compilers have usually trouble to implement such methods They often add an implementation of these
methods in all the executable code files whose source files have included their header files, even if the class
or the method is not used. This increases the size of all the modules that are client of this class.
Create inline default constructors

https://www.maruf.ca/files/caadoc/CAADocTechArticles/CAADocCppCodingRules.htm 5/19
4/17/2019 CAA V5 C++ Coding Rules

So never do this:
class MyClass : public MyParentClass
{
inline MyClass(int i) : MyParentClass(i,"WhyNot"),_MyPointer(NULL) {}
...
};

Constructor implementations must be put in the class source (.cpp) file.


Call other methods in inline methods

First, the inline advantage disappears, since calling a method branches to another location in the
executable code. Moreover, some compilers implement such method as a static one in all executable code
files whose source files include the header file containing the inline method. So never do this:
class MyClass
{
public :
inline int foo(int i) { return i*GetValue(); }
...
};

[Checklist | Top]

Do not Redefine Basic Operators

Except if it is OBVIOUS for everybody (complexes, points) .

Advice: before providing one, verify that your implementation respects their "natural" properties. For example,
one would expect the addition "+" operator to be commutative. Don't provide one which is not, such as for
character strings.

[Checklist | Top]

Do not Include non Declarative Code in Header Files

It is disastrous from a size perspective, and couples your code with your clients' code. They'll have to rebuild when
you modify this code.

Caution: Be aware that:


class C c;

in a header file outside a class definition IS NON declarative code, but executable code. It calls the class
constructor and actually creates an instance in all the classes that include this file.

For example:
#ifndef MyClass_h
#define MyClass_h

class MyClass
{
...
};
MyClass AnInstance;

#endif

This code creates an instance of MyClass for each source that includes this header file, along with an initialization
function called when the shared library or DLL is loaded into memory, and a destruction function called when
exiting, and that often core dumps. Prefer the following declaration:

https://www.maruf.ca/files/caadoc/CAADocTechArticles/CAADocCppCodingRules.htm 6/19
4/17/2019 CAA V5 C++ Coding Rules
extern ExportedByCATModuleName const MyClass AnInstance;

and insert in MyClass.cpp:


const MyClass AnInstance;

[Checklist | Top]

Do Not Use Implicit Casts

When you pass a class instance as a method parameter or in an expression, check that its type matches the
expected one, or use an explicit cast to get this type. Otherwise, the compiler attempts to implicitly cast the actual
type into the required one. Some compilers issue errors when two different ways of casting exist, thus leading to
an ambiguity. Some others take the casting decision for you, and don't issue an error. This is worse, since the
wrong result could be detected at run time only. By explicitly casting your instance into the appropriate type, you
keep control on what happens and you have knowledge of the actual conversion performed, without surprise.

For example, assume that the following class encapsulates the integer scalar type:
class MyInt
{
public :
MyInt(int iInt); // constructor
MyInt operator + (MyInt); // addition operator
operator int(); // conversion function to int
private :
int a;
};

The expression (x+1) is ambiguous since it can be interpreted as either


(x.operator int() + 1)

or
(x.operator + (MyInt(1)))

The first way of interpretation casts x into an int before using the int addition operator to add 1, and supply the
result as an int. The second way constructs a MyInt instance from the value 1, uses the MyInt addition operator to
add the two MyInt instances, and leads to a MyInt instance. The same ambiguity could happen if a constructor
and a conversion function could both be used to cast an object into another.

Here is the result of the compilation of such expressions:


MyInt y1 = x + 1; // error AIX, HP-UX, Windows / OK Solaris
int y2 = x + 1; // error AIX, HP-UX, Windows / OK Solaris
int y3 = int(x) + 1; // OK
MyInt y4 = x + (MyInt(1)); // OK

A way to make the compiler issue an error when such situations occur is to use the explicit prefix (unknown
with AIX and Solaris) for the constructor:
class MyInt
{
public :
explicit MyInt(int iInt); // constructor
MyInt operator + (MyInt); // addition operator
operator int(); // conversion function to int
private :
int a;
};

The expression (x+1)may issue a compiler error.


MyInt y1 = x + 1; // error HP-UX, Windows
https://www.maruf.ca/files/caadoc/CAADocTechArticles/CAADocCppCodingRules.htm 7/19
4/17/2019 CAA V5 C++ Coding Rules
int y2 = x + 1; // OK HP-UX, Windows
int y3 = int(x) + 1; // OK
MyInt y4 = x + (MyInt(1)); // error HP_UX / OK Windows

[Checklist | Top]

Use Legal Types Only

Legal types are types you can assign to your variables. They are classified in scalar types and non-scalar types.
Table 1: Scalar Types
CATBoolean Integer which can take the value TRUE (1) or FALSE (0). Since this type is not a native C++ type, its
definition must be included using the CATBoolean.h header of the System framework.
char Signed integer in the -27… 27-1 range
wchar_t UNICODE character
short Signed integer in the -215 … 215-1 range
int Signed integer in the -231 … 231-1 range. (Note: 64 bit, LLP64 (Windows) and LP64 (Solaris, AIX, HP-
UX) platforms agree on the following definition for int: "Signed integer in the -231 … 231 -1 range".
However, 64 bits ILP64 platforms define this type as "Signed integer in the -263 … 263 -1 range".
Since no ILP64 platform exists yet, we will ignore the compatibility issue between ILP64 and other 64
bit platforms and recommend the use of int.)
float Floating point 32 bit ANSI/IEEE 754-1985 number
double Floating point 64 bit ANSI/IEEE 754-1985 number
unsigned Unsigned integer in the 0…28 -1 range
char
unsigned Unsigned integer in the 0…216 -1 range
short
unsigned Unsigned integer in the 0…232 -1 range
int

See also Table 3 that summarizes how to use the available types when they are used as parameters.
Table 2: Non-scalar Types
char* non-NLS character strings are defined using this header.
wchar_t* Unicode character string
CATString Character string encoded using the ISO 10646 code page, also known as the 7-bit ASCII
CATUnicodeString Unicode character strings. CATUnicodeString must be used whenever the character string is
shown to the end user as part of the user interface, and thus must be translated in the end user
language. It must also be used for character strings that are not intended to be translated, but
that are directly expressed in the end user language, such as file names. Use CATString or char
for any other case.
enum {} Enumerated integer value.
<scalar type> Array of scalar elements. Can be of fixed or variable size. Fixed size arrays are defined using the
[size] * notation and not the [] notation when they are used as out parameters (float array[]*
would not be correct, whereas float array** is).

An array of three floats will be defined as:


float myFixedArray[3]

A variable size array of floats will be defined as:


float* myVariableArray
struct Structure made of one or more typed fields. The type of each field is restricted to the list of
authorized types defined in Table 1.
interface Object Modeler interface. When the exact type of an interface is not known, the type
CATBaseUnknown should be used (instead of void*).
CATListOf <X> Collection class to manage different kinds of lists
<interface>_var Smart pointers, also known as handlers, can be used only when a CATIA-supplied method
requests one as a parameter, or returns one. Do not create new ones.

https://www.maruf.ca/files/caadoc/CAADocTechArticles/CAADocCppCodingRules.htm 8/19
4/17/2019 CAA V5 C++ Coding Rules

See also Table 3 that summarizes how to use the available types when they are used as parameters.

[Checklist | Top]

Constrain Variables, Arguments and Methods by Using const

CAA methods must use the const C++ modifier to indicate the parameters which are not modifiable.

const can be used as follows for scalar types:


const int i; // error. i must be initialized
const int j = 5; // ok
const int * k; // int value can't be changed
int l = 5;
int * const m = &l; // m is a constant pointer, but pointed value can change
const int * const n = &l; // n is a constant pointer pointed to a constant value

This can be used with method parameters, especially for input parameters, or with returned values, and for data
members that must be initialized in the constructors. Member functions can be declared as const to operate on
constant objects.

[Checklist | Top]

Appropriately Use the Scope Resolution Operator (::)

Assume the following:


class A
{
...
virtual void m();
};
class B : public A
{
...
void m();
};
class C : public B
{
...
void f();
};

Do not call A::m() from C::f():


void C::f()
{
...
A::m(); // Forbidden
...
}

The version of the m method you execute might not fit your needs, since, being a C instance, your class instance
is also a B instance. You can either use B::m(); or m();.

[Checklist | Top]

Do not Create Exceptions

Exceptions may seem an easy and powerful way of handling exceptional situations in a given method, and possibly
to deal with classical errors, by transferring the control to another part of the application that is designed to do
this. It is usually the worst thing to do in large applications, since if any method can throw exceptions, any method
https://www.maruf.ca/files/caadoc/CAADocTechArticles/CAADocCppCodingRules.htm 9/19
4/17/2019 CAA V5 C++ Coding Rules

need them to catch them. The difficulty is what to do with exceptions the method I'm currently writing is not
aware of, and what could the methods that are calling it can do with the exceptions it throws. Usually, the answer
is nothing, and the exception goes up in the calling stack, up to the upper level that simply aborts. Use CAA V5
errors instead.

Nevertheless, some CATIA frameworks throws exceptions, as you should use the CATTry, CATCatch, and
CATCatchOthers macros to enclose your code that calls methods from these frameworks, and take appropriate
actions when such an exception occurs.

[Checklist | Top]

Lifecycle Rules

This set of rules deal with the lifecycle of the entities you will use.

Manage the Lifecycle of Interface Pointers

As a general rule, any interface pointer must be:

AddRef'd as soon as it is copied


Released as soon as it is not needed any longer.

A call to Release must be associated with each call to AddRef.

This rule applies as follows for method parameters:

1. For in parameters: the caller must have AddRef'd an interface pointer passed as a method in parameter. As
for any in parameter, the callee can only use the pointer, but cannot modify it, and must call neither AddRef
nor Release on this interface pointer. The caller calls Release when the method have returned and as
soon as the interface pointer is not used any longer.
...
CATDocument * pDoc = NULL;
HRESULT rc = CATDocumentServices::New("Part", pDoc);
...
CATInit * piInitOnDoc = NULL;
rc = pDoc->QueryInterface(IID_CATInit,
(void**) &piInitOnDoc); // AddRef called by QueryInterface
...
CATInit * pInitOnDoc2 = piInitOnDoc; // pInitOnDoc is copied into pInitOnDoc2
pInitOnDoc2->AddRef(); // and immediately AddRef'd

HRESULT = pDoc->CalledMethod(pInitOnDoc2); // Use pInitOnDoc2, but don't modify it


// No call to AddRef/Release

... // Use pInitOnDoc2


pInitOnDoc2->Release(); // pInitOnDoc2 is not needed any longer
...

The caller passes a valued and AddRef'd CATInit pointer to the callee that can only use the pointer, that is
call CATInit methods. The called method returns, the caller can go on using the pointer, and Releases it as
soon as it is not needed any longer.
2. For out parameters: the caller must not AddRef an interface pointer passed as a method out parameter.
This interface pointer must be passed as NULL. The possible value of the interface pointer is of no use to
the callee. The callee must call AddRef as soon as the interface pointer is valued, and the caller must call
Release. The caller uses the interface pointer when the method has returned and calls Release and as
soon as the interface pointer is not used any longer. This is the case, for example, when calling
QueryInterface:
...
CATDocument * pDoc = NULL;
HRESULT rc = CATDocumentServices::New("Part", pDoc);

https://www.maruf.ca/files/caadoc/CAADocTechArticles/CAADocCppCodingRules.htm 10/19
4/17/2019 CAA V5 C++ Coding Rules
...
CATInit * pInitOnDoc = NULL;
HRESULT rc = pDoc->QueryInterface(IID_CATInit, (void**)&pInitOnDoc)
// Expanded QueryInterface
HRESULT QueryInterface(const IID& iid, void** ppv)
{
...
*ppv = ...; // pInitOnDoc is copied
*ppv->AddRef(); // and immediately AddRef'd
...
}
... // Use pInitOnDoc
pInitOnDoc->Release(); // pInitOnDoc is not needed any longer
...

The caller passes a NULL CATInit pointer to QueryInterface, that values and AddRefs this pointer. The
caller uses it and Releases it as soon as it is not needed any longer.
3. For inout parameters: the caller must call AddRef before passing the interface pointer. The callee can
modify the interface pointer after having calling Release, and must AddRef the new interface pointer
value. Finally, the caller must call Release after the method returned when the interface pointer is not
needed any longer.
...
CATDocument * pDoc = NULL;
HRESULT rc = CATDocumentServices::New("Part", pDoc);
...
CATInit * piInitOnDoc = NULL;
rc = pDoc->QueryInterface(IID_CATInit,
(void**) &piInitOnDoc); // AddRef called by QueryInterface
...
CATInit * pInitOnDoc2 = piInitOnDoc; // pInitOnDoc is copied into pInitOnDoc2
pInitOnDoc2->AddRef(); // and immediately AddRef'd

HRESULT rc = pDoc->CalledMethod(&pInitOnDoc2)
// Expanded CalledMethod
HRESULT CalledMethod(CATInit ** ppv)
{
...
*ppv->Init() // Use pInitOnDoc2
...
*ppv->Release(); // Release pInitOnDoc2
*ppv = ...; // pInitOnDoc2 is revalued
*ppv->AddRef(); // and immediately AddRef'd
...
*ppv->Init() // Use again pInitOnDoc2
...
}
... // Use pInitOnDoc2
pInitOnDoc2->Release(); // pInitOnDoc2 is not needed any longer
...

The caller passes a copied and AddRef'd CATInit pointer to the callee, that can use it as is before modifying
its value. To modify the interface pointer, the callee first calls Release, copies another value in the pointer,
and calls AddRef. The interface pointer can then be used by the callee, and by the caller when the method
has returned. The caller Releases it as soon as it is not needed any longer.

[Checklist | Top]

Manage the Lifecycle of Objects That Are not Interface Pointers

As a general rule, associate a delete with each new, or a free with each malloc.

https://www.maruf.ca/files/caadoc/CAADocTechArticles/CAADocCppCodingRules.htm 11/19
4/17/2019 CAA V5 C++ Coding Rules

in parameters: the parameter must be allocated and freed by the caller


out parameters: the parameter must be allocated by the callee, and freed by the caller
inout parameters: the parameter must be allocated by the caller. The callee may use the parameter before
modifying it. If the parameter is a pointer, the callee can free and reallocate it before returning. The
parameter is finally deallocated by the caller.

For out parameters:

It is the responsibility of the caller to set the pointer to NULL before passing it to the callee
It is the responsibility of the callee, if it fails, to cleanup memory and reset the pointer to NULL before
passing it back to the caller

For inout parameters, if the callee fails:

It is the responsibility of the callee to either restore the pointer initial value or cleanup memory and reset
the pointer to NULL before passing it back to the caller
It is the responsibility of the caller to check that the pointer passed back can be used.

[Checklist | Top]

Always Pass Parameters to Methods Using the Following Table

Table 3: Available Types for Function Parameters


Type in out inout
CATBoolean const CATBoolean CATBoolean * CATBoolean *
iMyBoolean oMyBoolean ioMyBoolean
char const char iMyChar char * oMyChar char * ioMyChar
CATString const CATString CATString * CATString *
iMyString oMyString ioMyString
wchar_t const wchar_t iMyWChar wchar_t * oMyWChar wchar_t * ioMyWChar
CATUnicodeString const CATUnicodeString & CATUnicodeString & CATUnicodeString &
iMyUString oMyUString ioMyUString
short const short iMyShort short * oMyShort short * ioMyShort
int const int iMyInt int * oMyInt int * ioMyInt
CATLong32 const CATLong32 CATLong32 * CATLong32 *
iMyLong32 oMyLong32 ioMyLong32
float const float iMyFloat float * oMyFloat float * ioMyFloat
double const double iMyDouble double * oMyDouble double * ioMyDouble
unsigned char const unsigned char unsigned char * unsigned char *
iMyUChar oMyUChar ioMyUChar
unsigned short const unsigned short unsigned short * unsigned short *
iMyUShort oMyUShort ioMyUShort
unsigned int const unsigned int unsigned int * unsigned int *
iMyUInt oMyUInt ioMyUInt
CATULong32 const CATULong32 CATULong32 * CATULong32 *
iMyULong32 oMyULong32 ioMyULong32
char * const char * iMyChar char ** oMyChar char ** ioMyChar
CATString * const CATString * CATString ** CATString **
iMyString oMyString ioMyString
wchar_t * const wchar_t * iMyWChar wchar_t ** oMyWChar wchar_t ** ioMyWChar
CATUnicodeString * const CATUnicodeString * CATUnicodeString *& CATUnicodeString *&
(array of iMyUString oMyUString ioMyUString
CATUnicodeString's)
enum <name> const <name> iMyEnum <name> * oMyEnum <name> * ioMyEnum
{<value>}
<scalar type> [size] const <scalar type> * <scalar type> ** <scalar type> **
iMyArray oMyArray ioMyArray

https://www.maruf.ca/files/caadoc/CAADocTechArticles/CAADocCppCodingRules.htm 12/19
4/17/2019 CAA V5 C++ Coding Rules
const <scalar type>
iMyArray[3]
struct const CATStruct * CATStruct ** CATStruct **
iMyStruct oMyStruct ioMyStruct
interface const CATIXX * iCmpAsXX CATIXX ** oCmpAsXX CATIXX ** ioCmpAsXX

Note about CATUnicodeString: Never pass a single CATUnicodeString instance as a pointer, always use
references. Also do not use pass-by-value, as this may perform string data duplication. Ideally CATUnicodeString
should never be allocated on the heap (except for arrays of CATUnicodeString whose size is unknown at compile
time): this class is a value type meant to be used like a native type (it performs the correct copy-on-write
semantics to optimize string data sharing yet preserve the correct semantics, unlike the native char*/wchar_t*
types).

[Checklist | Top]

Always Initialize Your Pointers to NULL

Whenever you create a pointer to a class instance or to an interface, always intialize it to NULL. This ensures that
the pointer doesn't take a non-null value without you knowing, and that any part of the program uses the pointer
as if it were correctly set.

...
CATBaseUnknown * piBaseUnk = NULL;
... // assign a valid value

Pointers incorrectly valued is the main memory leak source.

[Checklist | Top]

Always Test Pointer Values Before Using Them

Whenever you use a pointer, first test its value against NULL before using it. This ensures that the pointer has a
valid value and that you can use it safely. Otherwise, if the pointer is NULL, the pogram crashes.
...
if ( NULL != piBaseUnk )
{
... // you can use the pointer safely
}
else if ( NULL == piBaseUnk )
{
... // you cannot use the pointer
}

Put NULL first preferably.

[Checklist | Top]

Always Set Pointers to Deleted Objects to NULL

Whenever you delete an object allocated using the new operator, or whenever you free a memory block allocated
using either the malloc, calloc, or realloc functions, immediately set the pointer to NULL. This ensures that this
pointer cannot be used any longer.
...
if ( NULL != pObject )
{
delete pObject;
pObject = NULL;
}
...
https://www.maruf.ca/files/caadoc/CAADocTechArticles/CAADocCppCodingRules.htm 13/19
4/17/2019 CAA V5 C++ Coding Rules
if ( NULL != pMemBlock )
{
free (pMemBlock) ;
pMemBlock = NULL;
}
...

[Checklist | Top]

Always Set Released Interface Pointers to NULL

Releasing an interface pointer means that you don't need it any longer, and thus that you don't intend to use it
again. To ensure that this pointer will never be used afterwards, set it to NULL as soon as you release it.
...
piBaseUnk->Release();
piBaseUnk = NULL;
...

[Checklist | Top]

Object Modeler Rules

This set of rules deal with the CAA V5 Object Modeler.

Never Implement the Same Interface Twice in the Same Component

Why? To satisfy the Determinism principle. Otherwise, a call to QueryInterface for this interface is
undetermined.
A call to QueryInterface must always be determinist. Here, querying an
IB pointer is undetermined. QueryInterface returns a pointer to a TIE to IB
implemented by either Ext1 or Ext2, depending on the run time context
(dictionary declaration order or shared library or DLL loading order). There is
no means for the caller of QueryInterface to know which pointer is
returned, and no means for QueryInterface to indicate which one is
returned.

[Checklist | Top]

Never Implement in a Component an Interface that OM-derives from Another Interface Already
Implemented in the Component

Why? To satisfy the Determinism Principle. Otherwise, a component could implement two interfaces that OM-
derive from the same interface using two different extensions. A call to QueryInterface to get the base
interface would then be undetermined.
Don't OM-derive IB and IC from IA. If Comp implements IB using Ext1,
and IC using Ext2, this can occur:

Why? To satisfy the Determinism Principle. This call is undetermined.


QueryInterface returns a pointer to a TIE to the IA interface
implemented by either Ext1 or Ext2, depending on the run time context.
Do: Only C++-derive IB and IC from IA. Thus Comp doesn't implement
IA. A call to QueryInterface for IA will return E_NOINTERFACE. How?

https://www.maruf.ca/files/caadoc/CAADocTechArticles/CAADocCppCodingRules.htm 14/19
4/17/2019 CAA V5 C++ Coding Rules

In the cpp file of the IB and IC interfaces, do not write:

CATImplementInterface(IB, IA);

but write this instead:

CATImplementInterface(IB, CATBaseUnknown);

Do Better: Let only IA be a C++ abstract class to share method signatures, but don't make it an interface.

How? Do not include the CATDeclareInterface macro and an IID in IA's header file, and do not provide any
cpp file for IA.

[Checklist | Top]

Appropriately Use Data and Code Extensions

Use data extensions if the extension class has data members. Otherwise, use code extensions.

Why? To save memory. The code extension is dedicated to extension without data members. A code extension
class is instantiated once for all the instances of the component it belongs to, while a data extension is instantiated
for each component's instance. This can save a lot of memory.

How? Declare a code extension using the CATImplementClass macro. Like any extension class, it should always
OM-derive from CATBaseUnknown. As a code extension class, it should never C++-derive from a data extension
class.
CATImplementClass(MyExtension, CodeExtension, CATBaseUnknown, MyImplementation);

Warning: Among other restrictions, chained TIEs can't be used with code extensions.

[Checklist | Top]

Always OM-Derive Your Extensions from CATBaseUnknown

Why? If you set another class instead of CATBaseUnknown, such as the class from which the extension class
C++-derives, you introduce an unnecessary additional node in the metaobject chain that can only decrease
performance.

How? This is done using the CATImplementClass macro with CATBaseUnknown or CATNull always set as the
third argument.
CATImplementClass(MyExtension, DataExtension, CATBaseUnknown, MyImplementation);

or

CATImplementClass(MyExtension, DataExtension, CATNull, MyImplementation);

[Checklist | Top]

Never C++-derive Extensions that Implement Several Interfaces

Why? If you create an extension that C++-derives from another extension that itself implements several
interfaces, you may instantiate useless objects. You or your component's clients might use methods of an inherited
interface not explicitly implemented by your component, but whose bodies come from the inherited extension. As
a result, your extension may have undesirable companion objects, and any client can get a pointer to an interface
implemented by these companion objects.

For example, suppose that you create Ext2 that implements IB for component Comp2. Ext2 derives from Ext1 that
implements IA and IB, but IA is of no use to you. Object Modeler inheritance makes Comp2 implement also IA, but
you have not included a TIE macro for IA. Everything is OK with IB, but assume that a client already has a pointer
to IC, and queries a pointer to IA.
https://www.maruf.ca/files/caadoc/CAADocTechArticles/CAADocCppCodingRules.htm 15/19
4/17/2019 CAA V5 C++ Coding Rules

Instead of getting a pointer to a TIE_IA on Ext2, as expected, the client gets it on a new instance of Ext1, even if
the dictionary is correctly filled in, that is, even if Impl2 declares that it implements IA. This means that in addition
to Ext1 instantiated as the base object for Ext2, that is (Ext1 *) Ext2, another instance of Ext1 is created by
QueryInterface, and is pointed to by the returned TIE_IA. The major problems are:

Ext1 is duplicated and successive QueryInterface calls to the same component to get an IA pointer will
create as many Ext1 duplicates
If you have overridden some methods of IA in Ext2, they will not be executed. Those of Ext1 will be
executed instead.

There are three solutions.

1. The recommended solution: Define and use only unit interfaces, that is,
interfaces that expose methods that must ALL be implemented. In this case,
there is no need to C++-derive Ext2 from Ext1.

2. Otherwise, if you need to derive from an extension that implements IB,


choose one that implements ONLY IB. Ext2a C++-derives from Ext1a, includes a
TIE_IB macro, and the interface dictionary contains the IB declaration for Impl2.

3. If you really can't do anything else, declare all the interfaces whose
implementations are inherited by Ext2 from Ext1. To do this, include a TIE macro
for IA and IB to Ext2, and correctly fill in the interface dictionary.

[Checklist | Top]

Correctly Use QueryInterface

To correctly use QueryInterface:

1. Initialize the pointer to the requested interface to NULL


2. Use the same type, that is the same interface to initialize the pointer and to retrieve it from
QueryInterface
3. Never use a smart pointer in place of the interface pointer
4. Test the returned code using the macros SUCCEEDED and FAILED. The output parameters of functions such
as QueryInterface are valid and usable if and only if SUCCEEDED returns TRUE. Never test the output
https://www.maruf.ca/files/caadoc/CAADocTechArticles/CAADocCppCodingRules.htm 16/19
4/17/2019 CAA V5 C++ Coding Rules

pointers. Always test the HRESULT value using SUCCEEDED before using the output pointers.

[Checklist | Top]

Do not Use Smart Interface Pointers

Smart interface pointers raise more problems than they solve.

[Checklist | Top]

Enable Interface Pointers and Smart Interface Pointers to Coexist

You will need sometimes to make interface pointers and smart pointers coexist, because, for example, you call a
function that returns an interface pointer you need to cast into a smart pointer to call another function. Here are
the rules to smooth over this coexistence.

Avoid retrieving an interface pointer while casting it as a smart interface pointer to the same interface
...
{
CATIXX_var spCATIXX = ::ReturnAPointerToCATIXX();
if (NULL_var != spCATIXX)
{
spCATIXX->Release(); // Release the returned interface
// pointer using spCATIXX
... // Use spCATIXX
}
...
} // spCATIXX is released

The reference count is incremented by the global function, and incremented again by the assignment
operator redefined by the smart pointer class. That is once too often. To decrement the reference count, you
can't use the returned interface pointer since you have no variable to handle it. You must then use the smart
pointer spCATIXX. The count decrements. When going out of scope, the smart interface pointer is deleted
and the count decrements again. Avoid doing that. Even if this is correct, you can easily skip from this
case to the next one. Do this instead.
...
{
CATIXX * pCATIXX = ::ReturnAPointerToCATIXX();
if (NULL != pCATIXX)
{
CATIXX_var spCATIXX = pCATIXX;
pCATIXX->Release(); // Release the returned interface pointer
if (NULL_var != spCATIXX)
{
... // Use spCATIXX
}
} // spCATIXX is released

https://www.maruf.ca/files/caadoc/CAADocTechArticles/CAADocCppCodingRules.htm 17/19
4/17/2019 CAA V5 C++ Coding Rules
...
}
Never retrieve an interface pointer while casting it as a smart interface pointer to another interface
CATIYY_var spCATIYY = ::ReturnAPointerToCATIXX();

You have cast the returned CATIXX pointer to a smart pointer to CATIYY. This returned pointer to CATIXX
couldn't be released, since you have no variable to handle it. The reference count will never reach 0, and the
component will never be deleted. Never do that. Do this instead.
CATIXX * pCATIXX = ::ReturnAPointerToCATIXX();
CATIYY_var spCATIYY = pCATIXX;
pCATIXX->Release();
Never cast a smart pointer to an interface pointer

CATIXX * pCATIXX = SmartPtrToCATIXX;

This is not smart, and you must call AddRef and Release on the interface pointer.
CATIXX * pCATIXX = ::ReturnASmartPtrToCATIXX();

The returned smart pointer is a volatile variable. This makes a core dump with unchained TIEs, or with any
TIE if the returned smart pointer is the only handle on to the component.

[Checklist | Top]

Correctly Fill in the Interface Dictionary

To correctly fill in the interface dictionary, follow the two rules below:

Rule 1: A component must declare:


The interfaces it implements, that is, the interfaces for which it declares a TIE in its implementation
class or extension classes (see Figure 1)
The interfaces ObjectModeler-inherited by these interfaces (see Figure 2).

A component must not declare the interfaces whose implementations are OM-inherited through OM
component inheritance.
Rule 2: If the code generated by the TIE macro and the code that implements the corresponding interface
are located in two different shared libraries or DLLs, you must declare, in the dictionary, the shared library
or DLL that contains the TIE code instead of the one that contains the interface implementation code.

Why? To satisfy the Determinism Principle. Depending on the run time context, that is, which shared libraries or
DLLs are loaded in memory, and on other interface dictionary declarations, QueryInterface may find a pointer
to the requested interface on an inherited implementation or extension, and not on the current one.
Figure 1

No need to declare IA for Cmp2. The dictionary must only include

Cmp1 IA LibCmp1
Cmp2 IB LibCmp2
Cmp2 IC LibCmp2

Figure 2
For example, when IB C++- and OM-derives from IA:

1. if Cmp2 doesn't declare that it implements IA in the interface dictionary


2. and if the TIE_IB macro code is located in a shared library or DLL that is different
from the one containing the IB implementation and that is not loaded at the
moment QueryInterface is called to get a pointer to IA from a pointer to IC

https://www.maruf.ca/files/caadoc/CAADocTechArticles/CAADocCppCodingRules.htm 18/19
4/17/2019 CAA V5 C++ Coding Rules

QueryInterface will return a pointer to a TIE to Cmp1 instead of a pointer to a TIE to


Cmp2. To avoid this, the dictionary must include:

Cmp1 IA LibCmp1
Cmp2 IA LibTIE_IBCmp2
Cmp2 IB LibCmp2
Cmp2 IC LibCmp2

[Checklist | Top]

References

[1] CAA V5 C++ Naming Rules

[Top]

History

Version: 1.0 [Jan 2000] Document created


[Top]

Copyright � 2000, Dassault Syst�mes. All rights reserved.

https://www.maruf.ca/files/caadoc/CAADocTechArticles/CAADocCppCodingRules.htm 19/19

You might also like