|
New C++ Features |
|
---|---|---|
|
||
This chapter describes KAI C++ features that are fairly recent additions to the language. It is intended as an introduction to these features, and not an exhaustive tutorial. These features are described in more detail by the ISO C++ standard.
For more information on the standards process, see the comp.std.c++ FAQ.
Occasionally, the new features conflict with old features used
by existing codes. See our migration
notes on how to deal with these conflicts.
Namespaces are implemented. The new keywords are namespace
and using
.
Some user-visible run-time routines are in the namespace std
,
as specified in the ISO Standard. The run-time routines
in these namespaces are declared in the following four header
files:
<new>
<exception>
<stdexcept>
<typeinfo>
To allow existing code without namespaces to use the run-time routines, there are four corresponding header files with .h suffixes.
<new.h>
<exception.h>
<stdexcept.h>
<typeinfo.h>
Each of these headers simply includes the corresponding ISO
header, and then imports the defined symbols from std
into the global namespace, via using
declarations.
If your code does not contain namespaces, but includes these headers
files, you should be able to compile it without difficulty.
The KAI C++ header files use the following symbols that are predefined by the KAI C++ compiler:
__EDG_RUNTIME_USES_NAMESPACES
__EDG_IMPLICIT_USING_STD
These are for internal use by KAI C++.
namespace Farm { struct Cow {}; void Milk(Cow&); } namespace City { void f( Farm::Cow& c) { Milk(c); } }The example requires argument dependent name lookup to compile the call site
Milk(c)
, since Milk
is defined
in a namespace that does not enclose the call site,
KAI C++ supports the advanced template features listed below.
Template friend
declarations and definitions are
permitted in class
definitions and class template
definitions. Below is an example of a class definition that declares
a template class Peer<T>
as a friend.
class Person { private: float my_money; template<class T> friend class Peer; };
Default arguments for type templates are permitted. For example, the following code is allowed:
template<class A, class B=double> class Pair { public: A a; B b; }; Pair<char,int> pci; Pair<char> pc; // B defaults to double.
Non-type template parameters for function templates are permitted. For example, the template below returns the number of columns in a two-dimensional array:
template<class T,int N> columns( T a[][N] ) { return N; }
The following fragment shows how it is used.
double y[30][40]; cout << columns(y);
The template parameters must be used within the declaration of the function's parameter type. Because the leading dimension of an array is not part of the type of an array, the following is not legal:
// ILLEGAL template<class T,int M> rows( T a[M][] ){ return; }
Templates may be explicitly instantiated with the Standard syntax. For instance, consider these templates:
template<class T> class Foo { // Template class public: template<class S> void noop( S& ); // Member template declaration }; template<class T> // Member template definition template<class S> void Foo<T>::noop( S& ) {} template<class T> void Bar( T x ) {} // Template function
Below are some sample explicit instantiations.
template class Foo<int>; // #1 template void Foo<char>::noop( short& ); // #2 template void Bar( float ); // #3
Line #1 forces instantiation of class Foo<int>
.
All non-template non-inline member functions and static data members
of class Foo<int>
will be instantiated. Line
#2 forces instantiation of member Foo<char>::noop(short&)
.
Only the member is instantiated. Line #3 forces instantiation
of function Bar(float)
.
The general syntax for explicit template instantiation is simply:
template declaration
where declaration is a non-template declaration.
The keyword typename
is now recognized inside
templates. The keyword is used inside templates to declare types
that are not template parameters nor declared in the enclosing
scope. The keyword as added by the ISO committee so that implementations
can do some parsing and type checking of template definitions
without instantiating them.
The following example demonstrates the sort of problem that
the keyword typename
solves.
template<class T> class Problem { public: void ack() { typename T::A * B; } };
Without the keyword typename, the compiler would not know whether
the member function is declaring B of type T::A*, or multiplying
A and B. The ISO rules require the use of typename even
in some spots where it might appear "obvious" that the
identifier is a type name. In particular, it is usually necessary
when using a qualified name in a typedef
.
template<class T> class Machine { public: typedef typename T::A gear_type; };
The identifier following typename
must be a qualified
identifier.
typename template
for member templatesThere is a further complication if the qualified name is a
member template identifier. If so, the ISO Standard (but not
the December 1996 draft) requires that the keyword template
preceed the qualified template name.
namespace std { // Excerpted from <memory> template<class T> struct allocator { template<class U> struct rebind { typedef allocator<U> other; }; }; } template<class Allocator> struct Decanter { typename Allocator::template rebind<int>::other int_allocator; ... };
In strict mode, KAI C++ 3.3 and later insist that the keyword template
appear as shown above in the declaration of int_allocator
.
If it is omitted, the ISO rules require that KAI C++ interpret the
member as a non-template member, and KAI C++ will complain:
template parameter "Allocator::rebind" may not have a template argument list
To summarize, here is the ISO grammar in Section 7.1.5.3 of the Standarfd for an elaborated type specifier:
elaborated-type-specifier: class-key ::opt nested-name-specifieropt identifier enum ::opt nested-name-specifieropt identifier typename ::opt nested-name-specifier typename ::opt nested-name-specifieropt templateopt template-id
// Class for N-element vector of type T. template<class T,size_t N> class Vec { public: // Template member declaration for assignment of conformable vectors. template<class U> Vec& operator=( const Vec<U,N>& rhs ); T& operator[]( size_t i ) { assert( i<N ); return rep[i]; } // Example of template friend. template<class U,size_t M> friend class Vec<U,M>; private: T rep[N]; };
The template member for operator=
allows conformable
vectors to be assigned as long as elementwise conversion is allowed.
For example, given:
Vec<int,3> u; Vec<float,3> v;
the assignment
v = u
will have the expected behavior.
If a template member of a template class is defined outside the class, there should be two ``template'' prefixes before the definition. Below is the definition for the operator= of the example.
// Definition for assignment. template<class T,size_t N> template<class U> inline Vec<T,N>& Vec<T,N>::operator=( const Vec<U,N>& rhs ) { for( size_t i=0; i<N; i++ ) rep[i] = rhs.rep[i]; return *this; }
The outer prefixes is for the template class; the inner prefix is for the template member. It sometimes helps to think of these as analogous to the "for all" (upside-down A) quantifiers in mathematics.
Template members allow member functions, member types, and friends to all be templates. The example above shows a template friend. Notice that the template friend grants friendship wider than necessary, because it grants friendship between non-conformable vectors. There was no way around this limitation until KAI C++ 3.3, which implements explicit specification of template arguments. The section on guiding declarations discusses the friend declaration issue in more detail.
An important point to remember when writing member templates
is that the compiler never uses them to generate an implicit copy-constructor
or assignment operator. Thus if a class requires a user-defined
copy-constructor or assignment operator, the class should define
them as non-template members, even if there is also a template
member that looks like it would stand in for the implicitly generated
operator. For an example of a class that was written this way,
take a look at the definition of class auto_ptr
in
KAI C++'s header file <memory>
.
However, template members are used during overload resolution, even when competing against implicitly generated operators. Thus a template member is used to copy and object if it provides a better match than the implicitly generated copy constructor. Below is an example that demonstrates this principle.
class Base { public: Base() {} template<class T> Base( T&); // Member constructor }; class Derived: public Base { }; Base b1; Derived d1; Base b2( b1 ); // uses implicitly generated constructor Base( const Base&) Base b3( d1 ); // uses template member constructor Base( const Derived&).
KAI C++ supports the ISO syntax for full specialization, which is really just a special case of the ISO syntax for partial specialization. Except in --strict mode, KAI C++ also allows the old Cfront syntax. A full specialization of a primary class template is an alternative definition for the class for certain values of template arguments.
For example, here is a definition for a template class with a method for copying blocks of data.
template<class T> class block_util { public: static void copy( T * dest, const T * src, int n ) { for( int i=0; i<n; i++ ) dest[i] = src[i]; } };
This completely general template is called the primary class
template. For character data, it is often more efficient to use
the standard library routine memcpy
, because memcpy
often employs obscure architecture-dependent tricks. To make the
compiler call memcpy
for characters, define a full
specialization of block_util<char>
as shown
below.
template<> class block_util<char> { public: static void copy( char * dest, char * src, int n ) { memcpy( dest, src, n ); } };
The old Cfront syntax is similar, except that the prefix template<>
would be missing.
Beginning with release 3.2, KAI C++ supports partial specialization of class templates. A partial specialization of a primary class template is an alternative definition for the class when the template arguments match the partial declaration of the template arguments in the partial specialization.
For an example, consider the class template block_util
from the previous section. It had a full specialization for the
special case of copying characters. It might also help to have
a special case for copying pointers to any type U. Below is a
partial specialization for class block_util
.
template<class U> class block_util<U*> { public: static void copy( U ** dest, U ** src, int n ) { memcpy( dest, src, n*sizeof(U*) ); } };
The syntax for a partial specialization looks quite similar
to that for primary class templates. The syntax that distinguishes
it from the primary class template is the <U*>
after the class name. The declaration tells the compiler that
for all types U, here is the definition to use for block_util<U*>
.
The template for a partial specialization can have fewer or more template parameters than the primary template. Here's an example from the ISO standard (Section 14.5.4 paragraph 4):
template<class T1, class T2, int I> class A { }; // #1 template<class T, int I> class A<T, T*, I> { }; // #2 template<class T1, class T2, int I> class A<T1*, T2, I> { }; // #3 template<class T> class A<int, T*, 5> { }; // #4 template<class T1, class T2, int I> class A<T1, T2*, I> { }; // #5
Template #1 is the primary template. The other templates are partial specializations of it.
Here's an example of a primary template and specialization with more template parameters than the primary template.
template<class T> class Rank { public: static const int value = 0; }; template<class T,int N> class Rank<T[N]> { public: static const int value = 1+Rank<T>::value; };
This template demonstrates the power of partial specialization to perform a non-trivial computations on types. The template computes the number of dimensions of a type. For example, the following fragment prints 0 1 2.
typedef float a; typedef float b[10]; typedef float c[20][30]; printf( "%d %d %d\n", Rank<a>::value, Rank<b>::value, Rank<c>::value );
The rank computation here is on types. A similar rank computation on objects is shown in the next section.
Declarations of partial specializations do not have default
arguments. The default arguments are taken from the primary template.
KAI C++ 3.2 and later support partial ordering of function templates. This is similar to partial specialization of class templates, except that there are no primary templates. That is why despite the similarity, the feature is not called partial specialization of function templates.
Here's an example adapted from the Standard (Section 14.5.5.2 paragraph 5):
template<class T> void f( T ); // #1 template<class T> void f( T* ); // #2 template<class T> void f( const T* ); // #3 void bar( const int * p ) { f(p); // Calls #3 }
The call f(p)
calls an instance of template #3
since (const T*)
is more specialized than the other
two declarations.
Below is template function that computes the rank of an array. It returns the number of dimensions for the type.
template<class T> inline int Rank( T arg ) { // Function for computing rank of array. return 0; // Rank of non-array is scalar. } template<class T> inline int Rank( T arg[] ) { return 1+Rank(arg[0]); }
For example, the fragment below prints 0 1 2.
float a = 0; float b[10]; float c[20][30]; printf( "%d %d %d\n", Rank(a), Rank(b), Rank(c) );
The rank computation is similar to the rank computation using class partial specialization, but with important semantic differences. The version using class partial specialization computes the rank of a type as a constant expression, which can be used, for example, to declare an array size. In contrast, the version using function partial ordering computes the rank of an object as a (non-constant) expression. The KAI C++ optimizer will optimize the expression to a constant, but since this is an optimization, and not a language feature, the expression cannot be used where a constant expression is required. E.g., the expression cannot be used to declared an array size. Despite this limitation, the rank computation is nonetheless useful because it can be used in run-time expressions, such as the following:
int * shape = new int[Rank(c)];
It would seem that rank computations on objects (rather than types) are limited to returning non-constant expressions. But if you like exploring this sort of thing, here is a puzzle. Indeed, consider this puzzle:
Construct a macro Rank(x), that given a auto variable x of arbitrary type, returns the rank of x as a compile-time constant that can be used to declare an array size. For example, macro Rank should make the following snippet legal:
void foo() { float d[40][50][60]; int x[Rank(d)]; }
We thought that the puzzle was impossible to solve in Standard C++. However, Esa Pulkkinen found a solution, based on the observation that wrapping a function call inside the operator sizeof provides a way to generate a function call for typing purposes without actually evaluating the function. Here's his solution:
namespace ArrayRank { template <int i> struct Sized { double array[i]; ; template <class T> struct rank_type { enum { value = 0 }; }; template <class T,int N> struct rank_type<T[N]> { enum { value = 1 + rank_type<T>::value }; }; template <class T> Sized< rank_type<T[1]>::value > type_deduction_helper(T x[]) { return Sized<rank_type<T[1]>::value>(); } } #define Rank(x) (sizeof(::ArrayRank::type_deduction_helper(x).array)/sizeof(double))
You may be able to gather enough intuition from the examples to charge ahead without reading the rest of this section. However, once empowered, you are likely to expand use of the partial ordering feature until the compiler reports problems of ambiguity. The cure for ambiguity is a deeper understanding of the partial ordering rules. The exact rules that define the partial ordering of functions are given in Section 14.5.5.2 of the ISO Standard. The rules are precise, but mysterious, so here is a more intuitive presentation. The partial ordering is based on a general substitution principle that says that a parametrized entity A is more general than entity B if there exist parameter substitutions for A that make it equal to B. For example, the quadratic formula a*x*x+b*x+c is more general than the linear formula b*x+c, because setting a=0 makes equal to the linear formula.
The partial ordering of function templates is specific to each call site. When determining which of two template functions A and B to use, the compiler first deduces the template parameters that make template B fit the call site. This fitting may involve implicit conversions. If a fit can be found, the compiler then sees if A fits exactly the same function arguments (and implicit conversions) that were used to fit B. No more implicit conversions are allowed during the second fitting. If such a fit can be found, then template A is at least as general as B. The compiler then reverses the roles of A and B to check if B is at least as general as A. If not, then template B is more specialized and is the template selected for the call site.
As with the rest of template matching in C++, the following type differences are ignored when matching the templates:
For instance, consider the following fragment:
template<class T> void g(T) {} // #1 template<Class T> void g(const T&) {} // #2 void caller() { int i; g(i); // Ambiguous }
The call is ambiguous because both the top-level reference
(&) and const qualifier under it are ignored for matching
purposes.
Common Misunderstanding About Partial Specialization
We've fielded multple queries about why code like following does not compile:
template<class T> // Template #1 T f(T i, T j) { return i + j; } template<class T> // Template #2 T f(T i, long j) { return i - j; } ...f(2L,2L)... // WRONG!The erroneous belief is that template #2 is more specialized than template #1, because
long
is less general than T
.
This sort of intuition generally works well when each template parameter
is used only once in a declaration. However, it is wrong when a
template parameter is used twice.
The ISO rules say that template #2 is more specialized than template #1 if
template #1 can match any instantiation of template #2.
But consider these two signatures:
float f(int,int); // Declaration #1 float f(float,long); // Declaration #2Templates #1 and #2 each match declarations #1 and #2 respectively, but not the other. Thus neither template is more specialized than the other.
The ISO C++ Standard removes the notion of guiding declarations. Programmers have been using guiding declarations for years without knowing this buzzword. Since absence of guiding declarations may change how programs behave, you need to know how to control whether KAI C++ allows them, what they were, and how to update programs to conform with the Standard.
The easy part is the command line options that control whether
KAI C++ recognizes guiding declarations. By default, they are enabled
unless in --strict
mode. The default can be overridden
by the command-line options --guiding_decls
and --no_guiding_decls
.
The reason that they are enabled by default in non-strict is that
the majority of existing codes may depend upon them.
The notion of guiding declarations arose from the old C++ model of templates. In this model, a program was completely compiled first, and then if any entities were missing, the compiler looked for templates that could be instantiated as the missing entities. In particular, there was no distinction between an ordinary function and a specialization of a template function. The ordinary function syntax was used to declare specializations. Furthermore, an ordinary function prototype could be used to declare a function that was supposed be instantiated from a template function. For instance, consider the following fragment.
template<class T> void f( T x ) {} // #1 void f( int x ); // #2 void f( void * x ); // #3In the old C++ model, if no definition was found to go with declaration on line #2, then the compiler would instantiate the template on the declaration on line #1 to provide the definition. The same would be done for line #3. Lines #2 and #3 were not, however, redundant because they participated in overload resolution. Given a function call, say
f(2)
, the compiler would
use lines #2 and #3 to guide overload resolution. Thus those lines
were later called guiding declarations.
To summarize: Syntactically, guiding declaration is a declaration
that would be a full specialization if it were prefixed by the
full specialization syntax template <>
. Semantically,
it is considered an instance of a template declaration, to be
used to guide overload resolution. Guiding declarations are not
part of ISO Standard.
Removing guiding declarations from a program requires a little thought. There are four options, and the right one depends upon the programmer's intent.
template<class T> void f( T x ) {} // Template function void f( int x ); // Ordinary function void f( void * x ); // Ordinary function
template<class T> void f( T x ) {} // Template function template<> void f( int x ); // Specialization of template template<> void f( void * x ); // Specialization of template
For purposes of discussion, below is a sample program that requires a friend declaration. The program prints how much money King Babar has.
#include <iostream> using namespace std; // Forward declaration of King required for forward declaration of operator<<. template<class T> class King; // Forward declaration of operator<< required for definition of class King. template<class T> ostream& operator<<( ostream& o, const King<T>& king ); template<class T> class King { T money; public: King( T value ) : money(value) {} // Guiding declaration friend ostream& operator<<( ostream&, const King<T>& ); }; template<class T> ostream& operator<<( ostream& o, const King<T>& king ) { // operator<< accesses private member king.money. return o << king.money; } main() { King<int> Babar(1000000); cout << babar << endl; }
This program compiles with KAI C++ in non-strict mode, but fails with a link-time error in strict mode. The problem is the guiding declaration, which is not recognized in strict mode. The error message deserves close attention:
Unresolved: operator <<(std::basic_ostream<char, std::char_traits<char>> &, const King<int>& );
Notice that there is no mention of template parameters here, which indicates that the missing function is a non-template function. This implies that the compiler resolved a call to a non-template prototype. The offending prototype is of course:
friend ostream& operator<<( ostream&, const King<T>& );
Though this prototype occurs inside a template, the prototype
itself is not a template nor a template instance. When guiding
declarations were enabled, KAI C++ saw this as an instance of the
template function. But with guiding declarations disabled, it
is an ordinary friend function prototype that is seen when class
King<T>
is instantiated for a type T. The
lack of a definition for this ordinary friend function results
in the unresolved symbol.
To fix the problem, the friend declaration must be changed so that it is an instance of the template and not a non-template function. In KAI C++ 3.3 and later, the right way to write it is use explicit specification of the template arguments:
friend ostream& operator<< <T>( ostream&, const King<T>&);
This says that the friend is operator<<
,
instantiated for the type T
for which class King
is being instantiated.
Informally, options 1-4 say that in order to conform to the Standard,
you must ``say what you mean.'' In our experience, updating codes
to the new template rules has been well worth the improvement
in clarity of how template instantiation and overload resolution
work.
KAI C++ 3.3 implements explicit specification of function template arguments (Section 14.8.1 of Standard). For example, consider the following template function:
#include <new> template<class T> T * QuickAlloc( char* &ptr ) { T * result = new(ptr) T; ptr += sizeof(T); return result; }
The template parameter T
cannot be deduced from
its arguments. Therefore the caller must explicitly specify the
template argument, such as shown below:
QuickAlloc<double>(ptr);
Template member functions may also have their template arguments explicitly specified. Below is an instance.
template<class A> class Pool { public: template<class T> T * alloc() {return 0;} }; void f() { Pool<double> pool; pool.alloc<int>(); // Explicit specification of int }
The explicit specification syntax is disallowed for two kinds of member template functions: constructors and conversion operators. For example, the following example is illegal because the caller cannot specify the template arguments for the constructor and the compiler cannot deduce it at any call site.
struct Foo { template<class T> Foo(); // ILLEGAL };
The rationale for the restriction on constructor member templates
is that the ``obvious'' calling syntax Foo<int>()
is already reserved to construct an instance of template class
Foo.
The situation for conversion operators is more of a syntactic quirk. Since conversion operators are invoked by the name of their return type instead of a function name, there is technically no function name to which to affix the specification. However, this is never a problem since the type name in the invocation is sufficient to indicate the template argument type. Below is an example from Section 14.5.2, paragraph 5, of the Standard:
struct A { template <class T> operator T*(); }; template <class T> A::operator T*(){ return 0; } template <> A::operator char*(){ return 0; } // specialization template A::operator void*(); // explicit instantiation int main() { A a; int* ip; ip = a.operator int*(); // explicit call to template operator A::operator int*() }
template <class T> void f(T, T = T()) {} struct NoDefault { NoDefault(int) {} // No default constructor }; void Demo() { f(NoDefault(1),NoDefault(2)) }Prior versions of KCC rejected the example because they tried to instantiate the default argument.
A weakness of C is that the same cast notation (T)x
is used for a multitude of purposes, some benign and some ``adventuresome.''
The new-style casts distinguish these purposes so that the compiler
can report misuses.
dynamic_cast
dynamic_cast
is always type safe. For example,
dynamic_cast<Foo*>(x)
converts x
to a ``pointer to Foo'' if x is a pointer to Foo, a class derived
from Foo, or a pointer to a base class of Foo. If the compiler
cannot prove that the cast is safe at compilation time, a run-time
check is inserted. If the run-time check fails and the cast is
to a pointer type, it returns NULL. If the run-time check fails
and the cast is to a reference type, the program throws an exception
of type std::bad_cast
(or if exception-handling
is turned off, the program aborts). To use dynamic_cast
to cast from a base class to a derived class, the base class
must have at least one virtual function, otherwise KCC will report
an error during compilation. Unlike old-style cast notation,
dynamic_cast
allows casting from virtual base classes
to derived classes. A dynamic cast cannot be used to remove qualifiers.
const_cast
const_cast<Foo*>(x)
converts x
to a ``pointer to Foo'' if x is a pointer to a (const Foo)
,
but not if x is a pointer to a (const Bar)
(unless
Bar is a synonym for Foo). Otherwise an error is reported during
compilation. Despite its name, const_cast
also can
add or remove volatile
qualifiers. With regard to
strong typing, it is always safe to add const
and
volatile
qualifiers with const_cast
,
but is unsafe to remove those qualifiers.
static_cast
static_cast<Foo*>(x)
converts x
to a ``pointer to Foo'' if x is a pointer to a base or derived
class of Foo. Otherwise an error is reported during compilation.
A static_cast
is not necessarily type safe,
because it allows unchecked downcasts from base classes to derived
classes.
reinterpret_cast
reinterpret_cast
in that it does not allow casting
a pointer to member of one class to a pointer to member of another
class if the classes are unrelated. Curiously, reinterpret_cast
can not be used for conversions for which the other
new-style casts suffice. You cannot simply change any C cast
to a reinterpret_cast
, unless it really is the dangerous
sort loved by the putative ``real programmer.''
Our recommendation is to always use dynamic_cast
where possible, as it is typesafe, and has the speed of static_cast
when it means the same thing. A static_cast
should
be reserved for casting downwards in a class hierarchy when a
dynamic_cast
is not possible or too slow. A const_cast
should be reserved for removing const
or volatile
qualifiers. In general, it should not be used to add such qualifiers
because the compiler should automatically add such qualifiers
where necessary. As for reinterpret_cast
, it is not
a subject for polite discussion. Use it to alert readers that
your code is impolite.
All versions of KAI C++ 3.x support run-time type identification,
which includes the operators dynamic_cast
and typeid
.
The operator typeid(T)
allows programs to inspect
types of objects. It is similar in syntax the C sizeof operator
in that it can be applied to types or expressions. For a type
T
, the expression typeid(T)
returns
an object of class std::type_info
corresponding to
type T
. Top-level qualifiers and reference modifiers
are not encoded in the std::type_info
. Below is an
example of this point from the Standard (5.2.8 paragraph 5):
class D { ... }; D d1; const D d2; typeid(d1) == typeid(d2); // yields true typeid(D) == typeid(const D); // yields true typeid(D) == typeid(d2); // yields true typeid(D) == typeid(const D&);// yields true
For an expression x
, the rules for the value of
typeid(x)
are a bit complicated. If x
is a reference to a class with at least one (possibly inherited)
declaration of a virtual functions, then the result corresponds
to the actual run-time type of x. Otherwise, the result corresponds
to the declared type of x. In both cases, top-level qualifiers
and reference modifiers are ignored. For example, consider the
following class hierarchy and initializations.
class X { // No virtual functions }; class Y: public X { // Virtual functions public: virtual void a_function() {} }; class Z: public Y { // Inherited virtual functions. }; const X * x = new Z; const Y * y = new Z; const Z * z = new Z;
Given these declarations, here's some sample expressions and
what KCC does with them. The precise spelling of string returned
by method std::type_info::name
depends upon the compiler.
typeid(*x).name()
"X"
, despite the fact
that x
really points to a Z
. The reason
is that *x is declared as a reference to an X
, and
since class X
has no virtual functions, operator
typeid
returns the declared type.
typeid(*y).name()
"Z"
, because *y
is declared as a reference to a Y
, class Y
has a virtual function, and y
really points to a
Z
.
typeid(*z).name()
"Z"
, because *z
is declared as a reference to a Z
, class Z
inherits a virtual function, and z
really points
to a Z
.
typeid(y).name()
"const Y *"
, because
the result for non-reference types is always the declared type.
The qualifier const
appears because it is under
a level of indirection and thus not a top-level qualifier.
Use of operator typeid
requires that the header
file <typeinfo>
(or <typeinfo.h>
)
be included by the source file. The header file <typeinfo>
defines class std:type_info
for users as follows:
namespace std { class type_info { public: virtual ~type_info(); bool operator==(const type_info& rhs) const; bool operator!=(const type_info& rhs) const; bool before(const type_info& rhs) const; const char* name() const; private: type_info(const type_info& rhs); type_info& operator=(const type_info& rhs); }; }
Copying and assignment of type_info objects is disallowed.
All you are allowed to do is compare them or inspect their names.
The method before
provides a collation order for
type_info
objects.
RTTI is supported for SGI platforms, except in old 2.05 versions of KCC that are link-compatible with SGI's current C++ compilers.
The ISO C++ Standard types bool
and wchar_t
are implemented.
True and false values are denoted by the keywords true
and false
respectively. KAI C++ implements bool such
that sizeof(bool)==1
, but you should not assume that
is true of other C++ compilers.
Library support for type wchar_t is not yet part of KCC.
The keyword mutable
is implemented. You can use it on
non-static data member declarations to indicate members that can
be changed even through const
references. This is
handy when you have objects that are conceptually const
,
but have an internal hidden state. Below is an example class MemoCos
that tries to reuse previous computation when possible.
class MemoCos { private: mutable double last_x; mutable double last_y; public: MemoCos() : last_x(0.0), last_y(1.0) {} double operator()( double x ) const { if( last_x!=x ) { // Recompute memorized value. last_y = cos(last_x=x); } // Use memorized value. return last_y; } };
Notice that operator()
is declared const
.
Without the keyword mutable
, it is not possible to
modify the data members last_x
and last_y
.
The ARM required that methods for a nested class be defined inside the enclosing class scope, which often made nested classes awkward to define. KAI C++ implements the rules in the Standard that allow methods for nested classes to be defined outside the enclosing scope. For example, KAI C++ accepts the following:
class Outer { public: class Inner { public: Inner( int n ); int value; }; }; Outer::Inner::Inner( int n ) { value = n; }
Static member constants of integral or enumeration type can have constant initializers. E.g.:
class Propane { enum answer {YES,NO}; static const answer flammable = YES; static const int chain_length = 3; };
The primary advantage of doing so is that if initialized this way, the static member member constants can be used anywhere that a integral constant is required. For example, such constants can be used to declare array bounds, as case labels, and as template parameters.
The keyword explicit
can be used to declare constructors
that should not be used as implicit conversions. Consider the
following example:
class Gold { public: Gold( int ); explicit Gold( char* ); }; void Compare( Gold x, Gold y ); void DoCompare() { Compare( 79, Gold("pyrite") ); }
The declarations of the two constructors tell the compiler
that it may implicitly convert an int
into a Gold
,
but not a (char*)
into gold. Thus the call above
to Compare
requires an explicit conversion of "pyrite"
into a Gold
.
Cfront restricted the friend syntax to friend declarations. ISO Standard C++ extends the syntax to allow direct definition of friends inside classes. Below is an example.
class Color { public: Color( float r, float g, float b ) : red(r), green(g), blue(b) {} // Function opposite is a friend, not a member. friend Color opposite( const Color& c ) { return Color( 1.0f-c.red, 1.0f-c.green, 1.0f-c.blue ); }; private: float red, green, blue; };
The function opposite
is defined inside the class
as a friend, not as a member. Here's a fragment showing a use
of it:
Color red(1,0,0); Color cyan = opposite(c);
As with members defined inside a class definition, friend definitions
are implicitly inline, so they should be used only for small functions
that benefit from inlining. Friend definitions work for template
classes too.
KAI C++ 3.2 supports covariant return types on virtual functions. This feature allows a derived class to override a virtual function with a function whose return type is a subtype of the return type in the base class. Below is an example:
class Base { public: virtual Base * clone() const {return new Base(*this);} }; class Derived: public Base { public: Derived * clone() const {return new Derived(*this);} };
The method Derived::clone
overrides Base::clone
.
The advantage is more accurate type checking and less need for
downcasts.
--[no_]class_name_injection
.
Class name is required by Chapter 9, paragraph 2 of the ISO C++ Standard.
When a class, say Foo
, is defined, the identifer Foo
is injected into the scope containing the definition, and the scope of class Foo
itself.
The most common sort of code to break are STL iterators that are declared as nested
class declarations that inherit from std::iterator
.
Here's an example of the mischief.
#include <iterator> template <class T> struct Collection { struct iterator : public std::iterator<std::random_access_iterator_tag,T> { T* current; }; struct const_iterator : public std::iterator<std::random_access_iterator_tag,T> { T* current; const_iterator (const iterator& bar) : current (bar.current) {} // Problem }; }; template struct Collection<int>KAI C++ 3.4 with
--strict
will reject this example, complaining
that there is no member bar.current
. What happened is that
when when processing the declaration of parameter bar
, KCC
had to look up the meaning of the identifier iterator
.
It searched upwards from the class scope for const_iterator
up through its base classes. When it looked at base class
std::iterator
, it found the class name iterator
that was injected when the instance of std::iterator
was defined.
The most common symptoms of the name injection issue are complaints about
nonexistent members or unallowed access.
The quickest way to fix the example is to insert a typedef for iterator
inside the definition of const_iterator
. E.g., change it to read:
... struct const_iterator : public std::iterator<std::random_access_iterator_tag,T> { typedef Collection::iterator iterator; // Clarifying typedef T* current; const_iterator (const iterator& bar) : current (bar.current) {} }; ...Then name lookup will find the typedef before proceeding to the base class that caused the problem.
The other way to fix the problem, which perhaps is more clear to casual readers,
is to write out iterator
as Collection::iterator
everywhere inside the definition of const_iterator
.
We used the latter approach to fix <deque>
and <list>
,
where we learned about the joy of name injection the hard way.
ptr
in the
example below.
class Hamilton { friend class Burr; Burr * ptr; };claiming that Burr is not defined. Indeed it is not, because by the ISO rules the friend declaration makes
Burr
a friend without injecting it
into the scope of class Hamilton.
The option --[no_]friend_injection
controls this feature.
The array creation operator operator new[]
and
array destruction operator operator delete[]
can
now be overloaded. These are separate from their corresponding
scalar operators. For example, you can overload all four as shown
below.
class Memory { public: void * operator new( size_t size ); void * operator new[]( size_t size ); void operator delete( void * ptr ); void operator delete[]( void * ptr ); };
Debuggers do not yet understand how to demangle the names for
array new and array delete. They show up as __nwa__...
and __dla__...
.
Declarations in the test condition of if
, switch
,
while
, and for
statements are supported.
For example, the following code is legal.
while( Object * x = getobject() ) { x->activate(); }
The scope of the variable declared in a for
-loop
is the loop. In Cfront, it was from the for
-loop
to the end of the enclosing scope. For example, the following
is legal:
for( int i=0; i<n; i++ ) { do_first_thing(); } for( int i=0; i<n; i++ ) { do_second_thing(); }
If you have old code that depends upon the old scope rules, the easiest way to fix it is to move the initialization to before the loop. For example, if the old code looked like this:
for( int i=0; i<n; i++ ) { ... } if( i ) { // Depends on old scope rule ... }
it can be fixed by moving the initialization like this:
int i=0; for( ; i<n; i++ ) { ... } if( i ) { // Okay by new scope rule ... }
In every case that we have encountered in real codes, in the
worst case the new scope rule cause KCC to reports errors. In
theoretically possible cases where the code is legal with both
scope rules, but would behave differently, KCC emits a diagnostic
warning.
void
to
return void expressions, for instance:
void noop(); void much_ado_about_nothing() {return noop();}
"abc"[2] = 'x'; // ILLEGAL
Const
and volatile
qualifiers are
meaningful on class rvalues. In particular, they are not ignored
for function return values. The following code illustrates this
point.
class Quark { public: void kind() const {cout << "const\n";} void kind() {cout << "not const\n";} }; const Quark f1() {return Quark();} Quark f2() {return Quark();} main() { f1().kind(); // Call f1, then print "const" f2().kind(); // Call f2, then print "not const" }
As Section 7.5 of the Standard says: ``Two functions types with difference language linkages are distinct even if they are otherwise identical.'' This change is a two-edged sword.
--strict
mode:
extern "C" typedef void (*pcf)(); typedef void (*pf)(); // C++ linkage by default void example_of_error() { pcf f; pf g; g = f; // ILLEGAL in --strict mode }
report_linkage
to report the linkage of its argument.
#include <iostream> extern "C" typedef void (*pcf)(); extern "C++" typedef void (*pf)(); void report_linkage( pcf ) { std::cout << "C linkage" << std::endl; } void report_linkage( pf ) { std::cout << "C++ linkage" << std::endl; } void main() { report_linkage((pcf)0); // prints "C linkage" report_linkage((pf)0); // prints "C++ linkage" }
There is an inconsistency in the KAI C++ 3.2 library in that the
linkages for functions in the standard library that are standard
C functions still have "C" linkage, but the new C++
variants have "C++" linkage. For example, the function
std::tan(double)
has "C" linkage, but the
function std::tan(float)
has "C++" linkage.
The ISO Standard requires that both have "C++" linkage.
The difference is only an issue if your application passes/assigns
pointers to these functions.
#include <climits> enum Extrema { low = LONG_MIN, high = LONG_MAX };
The KAI C++ 3.4 library is quite close to the ISO C++ Standard.
The major violation that we know of is that C names from vendor's C headers that are supposed
to be only in namespace std
are also in the global namespace.
We tried real hard on that one, and decided discretion was the better part of valor.
Wide characters and multibyte characters are also not implemented.
Please report any other discrepanicies that you find.
This section summarizes the evolution of the library.
It is not a tutorial, but merely a reminder that new features
exist, and some interfaces have evolved.
The library header files can all be compiled in --strict syntax mode, but --strict does not govern the conformance of the library headers to the standard template library specifications. KAI has added some extra methods (e.g. opfx was added to class ostream) for the sake of backwards compatibility. These ``additions'' are under the control of various macro symbols. Here's a table of the symbols and affected entities.
KAI_NONSTD_ALLOCATOR | allocator<void> |
KAI_NONSTD_CHAR_TRAITS | std::char_traits<char> |
KAI_NONSTD_BITSET | conversions between bitset and string |
KAI_NONSTD_COMPLEX | inv |
KAI_NONSTD_FSTREAM | basic_filebuf(FILE*) |
KAI_NONSTD_FUNCTIONAL | old STL adaptors (e.g. unary_compose) |
KAI_NONSTD_HEAP | Header heap.h |
KAI_NONSTD_IOS_BASE | ios_base::seek_dir |
KAI_NONSTD_IOSTREAM | ``withassign'' classes |
KAI_NONSTD_ITERATOR | old ISO drafts of <iterator> |
KAI_NONSTD_MAP | map, multimap |
KAI_NONSTD_OSTREAM | Cfront members of ostream |
KAI_NONSTD_SET | set, multiset |
KAI_NONSTD_STREAMBUF | Cfront members of basic_streambuf |
KAI_NONSTD_UTILITY | triple, restrictor |
KAI_NONSTD_VECTOR_TRAITS | vector |
KAI_NONSTD_VALARRAY_TRAITS | valarray |
__KAI_STRICT
) that
controlled all extensions. The new system lets you pick and choose your departure from
ISO practice.
We recommend migrating your code away from these extensions, with the possible exception of KAI_NONSTD_FSTREAM. That one seems to define a constructor that many people have demanded, and we are puzzled as to why ISO does not require it.
If you have trouble porting an existing code to compile under
KCC 3.4, please contact us so that we may help. The new KCC library
and compiler are way ahead of existing C++ implementations, so
we need your guidance in making KCC support both existing and
ISO C++ codes.
In KCC, the stream classes are templatized as required by the Standard.
Streams for type wchar_t
are not yet supported.
See here
about how to migrate old codes to the new stream classes.
The ISO C++ classes istream
and ostream
are
much more complicated than the Cfront versions, largely due to
the locale
feature. As a consequence, the KCC iostreams are
slower than Cfront's, but much more general with respect to internationalization.
For fast input and output where formatting is not needed, we strongly sugggest using
unformatted I/O (Section 27.6.1.3 and 27.6.2.6 of Standard).
While on the subject of streams, it's worth clarifying what opening a stream in ``binary mode'' means. It does not mean that values are written out as raw bits. For instance, the floating point value number 3.41 will still be written out in ASCII, not in IEEE binary format. What ``binary mode'' means, as opposed to ``text mode'', is that the bytes (once formatted) will be written out exactly as is. On Unix systems, ``binary mode'' and ``text mode'' act the same. (The Rationale document for ANSI C says so.) On some other operating systems, ``text mode'' may imply special conversions such as expanding newline delimiters into carriage-return/line-feed sequences.
ios_base::sync_with_stdio
ios_base::sync_with_stdio(false)
will make them unsynchronized.
The advantage of unsynchronized streams is that they run about 5x faster for formatted I/O.
setfill
setfill
had the signature:
In KCC 3.4, it has the ISO mandated tyep:template <class charT, class traits> basic_smanip <typename basic_ios<charT, traits>::int_type, charT, traits> setfill (typename basic_ios<charT, traits>::int_type); // KCC 3.3
where T5 represents an internal implementation type.template<class charT> T5 setfill(charT c) { return __kai::omanip_setfill<charT>(c); }
An unfortunate consequence of this change is that it breaks existing codes that used a non-character argument for setfill. Here's an example:
cout << setfill(65); // WRONG - argument has type int. cout << setfill((char)65); // Right. cout << setfill('A'); // Right.
istream
In KCC 3.2, the function getline
declared in <string>
counted characters, as required by the January 1996 draft. The
count was accessible via method gcount()
on the input
stream. Subsequent ISO drafts have removed this feature. Thus
to be in line with the December 1996 and final ISO Standard (Section
21.3.7.9, as of KCC 3.3 the aforementioned function
getline no long counts characters.
The header file <utility> now puts the template relational
operators in a separate namespace rel_ops
, as required by the ISO Standard.
To see the reason for the change,
consider the definition of template operator<=
in the library:
template <class T> inline bool operator<= (const T& x, const T& y) { return !(y<x); }
While this definition is correct if type T is totally ordered,
it is wrong if type T is partially ordered. By putting the operator
in namespace rel_ops
, programmers have control over
using it.
The class char_traits
is defined in <char_traits>.
This class replaces classes ios_traits
and string_char_traits
,
which disappeared in the December 1996 draft. This change should
be noticeable only if your code defines its own traits classes.
The class iterator_traits
is defined in <iterator>.
Iterator traits remove the need for the old (and circuitous) STL
practice of defining auxilary functions for the sake of getting
information about an iterator type. For example, the old STL was
littered with the following sort of idom, where auxilary function
__foo
exists only because function foo
has no way to ask for the distance type corresponding to iterator
type ForwardIterator.
template <class ForwardIterator, class DistanceType> __foo( ForwardIterator first, ForwardIterator last, Distance* ) { // Use 3rd parameter only for its type. Distance d = 0; // Set d to distance between first and last. distance( first, last, d ); ... } template<class ForwardIterator> foo( ForwardIterator first, ForwardIterator last ) { // Need to know distance type. // Old STL has no way to do this directly, // so pass result of function distance_type // to auxilarly function __foo. __foo( first, distance_type(first,last) ); }
Below is the correct way to write foo
.
It uses iterator_traits
::difference_type to get the
distance type, which is now subsumed by type difference_type
in the Standard.
template<class ForwardIterator> foo( ForwardIterator first, ForwardIterator last ) { // Set d to distance between first and last. iterator_traits<first>::difference_type d = distance( first, last ); ... }
The primary template for iterator_traits<Iterator>
in <iterator>
expects that the following types
be defined by Iterator:
std::Iterator::iterator_category std::Iterator::value_type std::Iterator::difference_type std::Iterator::reference std::Iterator::pointer
When Iterator is a pointer type, a partial specialization takes over to do the right thing.
Some functions in original HP STL disappeared in the ISO C++ Standard,
namely iterator_category
, distance_type
, and value_type
.
Their usage (and associated circuitous auxilary
functions) should be replaced by the iterator_traits style.
The KCC 3.2 library container templates allow user-defined
allocators. This is an extension by the ISO C++ Standard to the
orginal STL containers. For example, the class set is parametrized
as set<Key,Allocator>. The default Allocator is an instance
of the class std::allocator
, defined
in <memory>.
In most cases, you should write your allocator as a template
class, even if your container is going to hold only one type.
For example, for a type set<double,DoubleAllocator>
it is not enough for DoubleAllocator to allocate objects of type
double
only. The reason is that class set
needs to allocate internal objects of another type that contain
type double as a subobject. It does this by using the equivalent
of the following computation to get an allocator for type U from
the allocator user_allocator
for type double that
the user supplied:
DoubleAllocator::rebind<U>::other internal_allocator(user_allocator);
This expression constructs an allocator object called internal_allocator
for objects of type U. For this expression to work, two features
of class DoubleAllocator
are required:
DoubleAllocator::rebind<U>::other
must return the type for an allocator similar to DoubleAllocator
,
but for type U.
double
.
If your allocator class is a template MyAllocator<T>
,
then it looks something like below:
template MyAllocator<class T> { ... template<class U> MyAllocator( const MyAllocator<U>& alloc ) { Construct *this, which should allocate objects of type T using same algorithm/policy as allocatoralloc uses for type U. } struct rebind { typedef MyAllocator<U> other; }; ... }; typedef MyAllocator<double> DoubleAllocator;
Of course in principle, you could define Allocator::rebind<U>::other
as some other completely different allocator, but in practice
it is simplest to define allocator types as template classes so
that writing the member type rebind is trivial.
Remember to make sure that MyAllocator<U>
allocates memory with alignment appropriate for type U. For example,
if writing an allocator for type char
, which has
no alignment requirement itself, the rebound allocator may be
allocating objects with quite different alignment requirements.
As of KCC 3.4, the containers are up to date with
respect to their use of member function Allocator::deallocate
.
The Standard that Allocator::deallocate
takes two arguments. Early drafts of the standard had one argument.
The two argument form must be used, and the second argument
must be correct.
stack
The Standard version of template class stack
differs from the
old STL version. The old version took a container class as its
argument. The Standard version supported by KCC takes two
arguments: the element type and (optionally) the container type.
The container type defaults to deque
. Here's what
the new prototype for class stack looks like:
namespace std { template <class T, class Container = deque<T> > class stack; }
So, for instance, below are the old and new ways to write a stack of int.
stack< deque <int> > old_pile1; // OBSOLETE STL - not supported by KCC stack< vector <int> > old_pile2; // OBSOLETE STL - not supported by KCC stack< int > new_pile1; // Standard style using default deque for container stack< int, vector <int> > new_pile2; // Standard style with explicit container
complex
The class complex<T>
takes a parameter T
that
describes what type to use for the real and imaginary parts. Here
are some sample declarations:
complex<float> x; // single-precision complex complex<double> y; // double-precision complex complex<int> z; // Gaussian integer
The Standard defines the behavior of complex<T>
only for
floating types. The behavior for other types (such as for Gaussian
integers) is unspecified by the Standard, but we attempt nonetheless
to make complex<T>
behave in a reasonable manner
for such types.
Judicious use of typedefs can ease the transition to the templatized class complex.
bitset
and basic_string
The members of class std::bitset that permitted conversions
to and from strings are now properly templatized in KCC 3.3 and
deal with any instance of template std::basic_string. In KCC 3.2,
they were restricted to std::string. Specifically, the constructor
for bitset
that took a std::string argument in KCC
3.2 is now templatized to work for any kind of std::basic_string.
Method bitset<N>::to_string that returned a std::string
in KCC 3.2 is now templatized to return any instance of std::basic_string.
The good news is that the changes bring std::bitset in agreement
with the Standard. The bad news is that the changes break
existing KCC 3.2 code that depended on the affected members.
To ameliorate the migration problem, you can use -DKAI_NONSTD_BITSET=1
to get the old members.
Here are the details for migrating from KCC 3.2. Because the constructor is now a template member, the compiler is not allowed to invoke certain implicit conversions that were previously allowed. The annoying consequence is that direct construction of a bitset from a (const char*) is now prohibited in examples such as shown below:
std::bitset<8U> foo("10101011"); // NO LONGER LEGAL
The compiler will report that no constructor of bitset matches. (Explanation for template mavens: The requisite template argument deduction for the std::basic_string temporary ignores implicit conversions.) To repair this sort of code, add an explicit conversion to std::string as shown below.
std::bitset<8U> foo(std::string("10101011"));
The fix for the change to bitset<N>::to_string
is a matter of explicitly specifying the template arguments. For
instance, if you had this:
b.to_string(); // NO LONGER LEGAL
simply add the explicit template arguments like this:
b.to_string<char,std::char_traits<char>,std::allocator<char> >()
valarray
valarray<T>
has been extensively rewritten to
conform to the ISO C++ Standard. It is also much faster than the KCC 3.3 version.
In KCC 3.2, class valarray<T>
could be implicitly
converted to a (T*)
or (const T*)
. These
members appeared in older drafts, but were removed by ISO in recent
drafts. The reason is that the presence of these members caused
an overload ambiguity conflict with operator[]
.
<valarray>
,
but are not part of the final C++ Standard:
length()
fill(const T& t)
free()
mult()
KAI_NONSTD_VALARRAY=1
.
slice_array
and similar proxies to be ``private''.
Sadly, the standard was not tested with a compiler that correctly
implements Section 12.2 Paragraph 1 of the standard.
The problem was even pointed out in comp.lang.c++ when the second final draft
was circulated, to no avail. With a conforming compiler the following is illegal:
void foo( std::valarray<float> &v, std::slice &s ) { v[s] += v[s]; }because the compiler must be permitted to copy-construct a temporary from the result of
v[s]
. Thus the specification
in the ISO C++ Standard makes class std::valarray
practically useless. KCC 3.3 worked around this problem in the
draft by declaring these copy constructors ``public''.
KCC 3.4 favors standard conformance. To get the old behavior,
define KAI_NONSTD_VALARRAY=1
.
gslice
no longer sorts indices gslice
(provided by Modena)
quirkily sorted the generated linear indices.
We can find no justification in any recent draft or final standard that justifies
this behavior. KAI has completely rewritten gslice
,
and it no longer exhibits the sorting behavior.
slice
and gslice
classes
are now about twice and three times faster respectively than before.
valarray
valarray
is useful for high-performance
computation? In our opinion, the answer is no. There are much better alternatives.
Class valarray
was an attempt to provide APL/F90 style programming in C++.
Section 26.3.1 paragraph 2 claims that:
The valarray array classes are defined to be free of certain forms of aliasing, thus allowing operations on these classes to be optimized.Unfortunately, the "free of certain forms of aliasing" is really only of interest to vector processors. For modern microprocessors, the advantages of the non-aliasing are overwhelmed by other considerations. Consider the following:
void add( valarray<float>& a, valarray<float>& b, valarray<float>& c ) { a = b + c; }The compiler has no way of knowing if array
a
is aliased with b
or c
. Thus it must dynamically allocate storage for array a
.
This sort of overhead can swamp the alleged gains.
It is true, in theory, that implementations could optimize away temporary arrays in
expressions such as:
a = (b * c) + d;Indeed, there are template-metaprogramming implementations of
valarray
that do so.
We chose not to use template-metaprogramming for our implementation, because it tends to
also cause slow compilation. It is unlikely that we (or anyone else, for that matter)
are going to optimize class valarray
further because:
If you are really looking for the ultimate in numerical performance in C++,
we suggest using KAI C++ with the Blitz or
MTL libraries.
In conjunction with KAI C++, they achieve speeds comparable and sometimes
in excess of FORTRAN speeds.
They rely on template metaprogramming.
These libraries are not provided by KAI, but can be downloaded from the Web links given.
auto_ptr
auto_ptr
is based on the final ISO C++ Standard,
unlike ealier versions based on earlier drafts.
Scott Meyers has an
excellent discussion
of how auto_ptr
ended up the way it is.
Once consequence of the final ISO C++ auto_ptr
is that it is no longer
considered CopyConstructible (Section 20.1.3 of Standard), thus it is now illegal
to put auto_ptr
in a standard container.
For instance, vector<auto_ptr<T> >
is forbidden.
allocator
std::allocator<T>
is faster in KCC 3.4.
The price you pay is that the argument to method deallocate
that specifies
the size of the array must be correct.
The signature of method std::allocator<T>::deallocate
is:
void deallocate(pointer p, size_type n);The argument n was ignored in KAI C++ 3.3, but used to advantage in KAI C++ 3.4. The new allocator keeps free lists of blocks of deallocated memory, and there are separate lists for different sizes. The size argument determines which list gets the block; indeed that is the Standard's reaons for having the argument. If the size is incorrectly specified as larger than the actual size of the deallocated block, a subsequent call to method
allocate
might allocate one of these
misclassifed blocks, and heap havoc will certainly ensue.
get_temporary_buffer
std::get_temporary_buffer
may be called to allocate another buffer
while a previously allocated buffer is still active. This is in conformance to what the
ISO Standard says. KCC 3.3 and many other STL implementations still inherit the original STL's
use of a single global block of memory for the buffer, which while faster, does not
conform to the Standard. (And frankly, was an appalling throwback to days of non-reentrant
non-thread-safe code.) The price is that the standard-conforming version
is slower, no faster than a malloc.