CAA V5 C++ Coding Rules
CAA V5 C++ Coding Rules
CAA V5 C++ Coding Rules
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 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]
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
#endif
[Checklist | Top]
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:
https://www.maruf.ca/files/caadoc/CAADocTechArticles/CAADocCppCodingRules.htm 1/19
4/17/2019 CAA V5 C++ Coding Rules
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]
[Checklist | Top]
[Checklist | Top]
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
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]
[Checklist | Top]
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]
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]
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]
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]
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()
...
[Checklist | Top]
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]
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]
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.
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) {}
...
};
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]
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]
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.
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;
[Checklist | Top]
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;
};
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.
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;
};
[Checklist | Top]
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).
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]
CAA methods must use the const C++ modifier to indicate the parameters which are not modifiable.
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]
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]
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.
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
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]
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
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
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]
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]
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
[Checklist | Top]
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
}
[Checklist | Top]
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]
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]
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:
https://www.maruf.ca/files/caadoc/CAADocTechArticles/CAADocCppCodingRules.htm 14/19
4/17/2019 CAA V5 C++ Coding Rules
CATImplementInterface(IB, IA);
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]
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]
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
[Checklist | Top]
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.
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.
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]
pointers. Always test the HRESULT value using SUCCEEDED before using the output pointers.
[Checklist | Top]
[Checklist | Top]
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
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]
To correctly fill in the interface dictionary, follow the two rules below:
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
Cmp1 IA LibCmp1
Cmp2 IB LibCmp2
Cmp2 IC LibCmp2
Figure 2
For example, when IB C++- and OM-derives from IA:
https://www.maruf.ca/files/caadoc/CAADocTechArticles/CAADocCppCodingRules.htm 18/19
4/17/2019 CAA V5 C++ Coding Rules
Cmp1 IA LibCmp1
Cmp2 IA LibTIE_IBCmp2
Cmp2 IB LibCmp2
Cmp2 IC LibCmp2
[Checklist | Top]
References
[Top]
History
https://www.maruf.ca/files/caadoc/CAADocTechArticles/CAADocCppCodingRules.htm 19/19