devxlogo

The Top 20 C++ Tips of All Time

The Top 20 C++ Tips of All Time

he following tips are a collection of general hands-on techniques and recondite pieces of knowledge not associated with a specific platform, programming domain, or compiler. As such, they can be of use to all C++ programmers. I grouped the tips into five major categories: general guidelines for coding style, memory management, performance enhancement, object-oriented design, and the Standard Template Library (STL).

What makes these tips special is that the information they provide usually cannot be found in C++ books or Web sites. For example, pointers to members are one of the most evasive, tricky, and bug-prone issues for even advanced users. Yet, they are a widely used C++ feature. Likewise, the discussion on STL terminology and iterator categories is another example of useful yet evasive information that isn’t readily available, unless you’re an STL expert.

The tips herein do not only explain how to write better code, but rather, they present the rationale behind the new language rules. Obviously, there are many other perennial good tips that C++ programmers can benefit from. However, I’m sure that this collection will provide you with many insights and guidelines for professional, efficient, and bug-free C++while teaching you some new coding and design techniques.

First Four: Guidelines for Better Coding Style
Under this category, I grouped tips that address frequently asked questions from C++ programmers of all levels of expertise. It’s surprising to discover how many experienced programmers are still unaware of the deprecation of the .h notation of standard header files, the proper usage of namespaces, and the rules regarding binding of references to temporary objects, for example. These issues and others will be discussed here. First, we start by explaining the difference between the deprecated header names and the modern, standard-compliant header-naming notation. Next, we explore a few dark corners of C++ whichdue to compilers’ limitations and the somewhat recondite nature of the associated language rulestend to confuse many programmers, e.g., the notion of comma-separated expressions and the rules of binding references to rvalues. Finally, we will learn how to invoke a function prior to a program’s startup.

Tip 1: or ?
Many C++ programmers still use instead of the newer, standard compliant library. What are the differences between the two? First, the .h notation of standard header files was deprecated more than five years ago. Using deprecated features in new code is never a good idea. In terms of functionality, contains a set of templatized I/O classes which support both narrow and wide characters, as opposed to which only supports char-oriented streams. Third, the C++ standard specification of iostream’s interface was changed in many subtle aspects. Consequently, the interfaces and implementation of differ from those of . Finally, components are declared in namespace std whereas components are global.

Because of these substantial differences, you cannot mix the two libraries in one program. As a rule, use unless you’re dealing with legacy code that is only compatible with .

Tip 2: Binding a Reference to an Rvalue
Rvalues and lvalues are a fundamental concept of C++ programming. In essence, an rvalue is an expression that cannot appear on the left-hand side of an assignment expression. By contrast, an lvalue refers to an object (in its wider sense), or a chunk of memory, to which you can write a value. References can be bound to both rvalues and lvalues. However, due to the language’s restrictions regarding rvalues, you have to be aware of the restrictions on binding references to rvalues, too.

Binding a reference to an rvalue is allowed as long as the reference is bound to a const type. The rationale behind this rule is straightforward: you can’t change an rvalue, and only a reference to const ensures that the program doesn’t modify an rvalue through its reference. In the following example, the function f() takes a reference to const int:

void f(const int & i);int main(){ f(2); /* OK */}

The program passes the rvalue 2 as an argument to f(). At runtime, C++ creates a temporary object of type int with the value 2 and binds it to the reference i. The temporary and its reference exist from the moment f() is invoked until it returns; they are destroyed immediately afterwards. Note that had we declared the reference i without the const qualifier, the function f() could have modified its argument, thereby causing undefined behavior. For this reason, you may only bind references to const objects.

The same rule applies to user-defined objects. You may bind a reference to a temporary object only if it’s const:

struct A{};void f(const A& a);int main(){ f(A()); /* OK, binding a temporary A to a const reference*/}

Tip 3: Comma-Separated Expressions
Comma-separated expressions were inherited from C. It’s likely that you use such expressions in for- and while-loops rather often. Yet, the language rules in this regard are far from being intuitive. First, let’s see what a comma separated expression is.

An expression may consist of one or more sub-expressions separated by commas. For example:

if(++x, –y, cin.good()) /*three expressions*/

The if condition contains three expressions separated by commas. C++ ensures that each of the expressions is evaluated and its side effects take place. However, the value of an entire comma-separated expression is only the result of the rightmost expression. Therefore, the if condition above evaluates as true only if cin.good() returns true. Here’s another example of a comma expression:

int j=10; int i=0;while( ++i, –j){ /*..repeat as long as j is not 0*/}

Tip 4: Calling a Function Before Program’s Startup
Certain applications need to invoke startup functions that run before the main program starts. For example, polling, billing, and logger functions must be invoked before the actual program begins. The easiest way to achieve this is by calling these functions from a constructor of a global object. Because global objects are conceptually constructed before the program’s outset, these functions will run before main() starts. For example:

class Logger{public:  Logger()   {    activate_log();  }};Logger log; /*global instance*/int main(){  record * prec=read_log();  //.. application code}

The global object log is constructed before main() starts. During its construction, log invokes the function activate_log(). Thus, when main() starts, it can read data from the log file.

Five to Eight: Memory Management
Undoubtedly, memory management is one of the most intricate and bug-prone issues in C++ programming. The power of accessing raw memory directly, the ability to allocate storage dynamically, and the utmost efficiency of C++ dictate very strict rules that you must follow in order to avoid memory-related bugs and runtime crashes.

Pointers are the primary means of accessing memory. C++ has two major categories of them: pointers to data and pointers to function. The second category is further divided into two subcategories: ordinary function pointers and pointers to members. In the following tips, we will explore these issues in depth and learn some technique to streamline the use of pointers while hiding their unwieldy syntax.

Pointers to functions are probably one of the least readable syntactic constructs of C++. The only less readable construct seems to be pointers to members. The first tip will teach you how to improve the readability of ordinary pointers to functions. This serves as the prerequisite for dealing with C++ pointers to members.

Next, we learn how to avoid memory fragmentation and its woeful consequences. Finally, we discuss the proper use of delete and delete[] operatorsstill, a fertile source of bugs and misconceptions.

Tip 5: Hiding the Cumbersome Syntax of Pointers to Functions
Can you tell what the following declaration means?

void (*p[10]) (void (*)());

p is an “array of 10 pointers to a function returning void and taking a pointer to another function that returns void and takes no arguments.” The cumbersome syntax is nearly indecipherable, isn’t it? You can simplify this declaration considerably by using typedefs. First, declare a typedef for “pointer to a function returning void and taking no arguments” as follows:

typedef void (*pfv)();

Next, declare another typedef for “pointer to a function returning void and taking a pfv”:

typedef void (*pf_taking_pfv) (pfv);

Now declaring an array of 10 such pointers is a breeze:

pf_taking_pfv p[10]; /*equivalent to void (*p[10]) (void (*)()); but much more readable*/

Tip 6: All About Pointers to Members
A class can have two general categories of members: function members and data members. Likewise, there are two categories of pointers to members: pointers to member functions and pointers to data members. The latter are less common because in general, classes do not have public data members. However, when using legacy C code that contains structs or classes that happen to have public data members, pointers to data members are useful.

Pointers to members are one of the most intricate syntactic constructs in C++, and yet, they are a very powerful feature too. They enable you to invoke a member function of an object without having to know the name of that function. This is very handy implementing callbacks. Similarly, you can use a pointer to data member to examine and alter the value of a data member without knowing its name.

Pointers to Data Members
Although the syntax of pointers to members may seem a bit confusing at first, it’s consistent and resembles the form of ordinary pointers, with the addition of the class name followed by the operator :: before the asterisk. For example, if an ordinary pointer to int looks as follows:

int * pi; 

You define a pointer to an int member of class A as follows:

int A::*pmi; /* pmi is a pointer to an int  member of A*/

You initialize a pointer to member like this:

class A{public:  int num;  int x;};int A::*pmi = & A::num; /* 1 */

The statement numbered 1 declares a pointer to an int member of class A and initializes it with the address of the member num. Using pmi and the built-in operator .* you can examine and modify the value of num in any object of class A:

A a1, a2;int n=a1.*pmi; /* copy a1.num to n */a1.*pmi=5; /* assign the value 5 to a1.num */a2.*pmi=6; /* assign the value 6 to a2.num */

If you have a pointer to A, you need to use the built-in operator ->* instead:

A * pa=new A;int n=pa->*pmi;  pa->*pmi=5;  

Pointers To Member Functions
These consist of the member function’s return type, the class name followed by ::, the pointer’s name, and the function’s parameter list. For example, a pointer to a member function of class A that returns an int and takes no arguments is defined as follows (note that both pairs of parentheses are mandatory):

class A  {public:  int func ();  };int (A::*pmf) (); 

In other words, pmf is a pointer to a member function of class A that returns int and takes no arguments. In fact, a pointer to a member functions looks as an ordinary pointer to function, except that it also contains the class’s name immediately followed by the :: operator. You can invoke the member function to which pmf points using operator .*:

pmf=&A::func;A a;(a.*pmf)();  /* invoke a.func() */

If you have a pointer to an object, you use the operator ->* instead:

A *pa=&a;(pa->*pmf)();  /*calls pa->func() */

Pointers to member functions respect polymorphism. Thus, if you call a virtual member function through such a pointer, the call will be resolved dynamically. Note, however, that you can’t take the address of a class’s constructor and destructor.

Tip 7: Avoiding Memory Fragmentation
Often, applications that are free from memory leaks but frequently allocate and deallocate dynamic memory show gradual performance degradation if they are kept running for long periods. Finally, they crash. Why is this? Recurrent allocation and deallocation of dynamic memory causes heap fragmentation, especially if the application allocates small memory chunks. A fragmented heap might have many free blocks, but these blocks are small and non-contiguous. To demonstrate this, look at the following scheme that represents the system’s heap. Zeros indicate free memory blocks and ones indicate memory blocks that are in use:

100101010000101010110

The above heap is highly fragmented. Allocating a memory block that contains five units (i.e., five zeros) will fail, although the systems has 12 free units in total. This is because the free memory isn’t contiguous. On the other hand, the following heap has less free memory but it’s not fragmented:

1111111111000000

What can you do to avoid heap fragmentation? First, use dynamic memory as little as possible. In most cases, you can use static or automatic storage or use STL containers. Secondly, try to allocate and de-allocate large chunks rather than small ones. For example, instead of allocating a single object, allocate an array of objects at once. As a last resort, use a custom memory pool.

Tip 8: Don’t Confuse Delete with Delete []
There’s a common myth among programmers that it’s OK to use delete instead of delete [] to release arrays built-in types. For example,

int *p=new int[10];delete p; /*bad; should be: delete[] p*/

This is totally wrong. The C++ standard specifically says that using delete to release dynamically allocated arrays of any type yields undefined behavior. The fact that on some platforms, applications that use delete instead of delete [] don’t crash can be attributed to sheer luck: Visual C++, for example, implements both delete[] and delete for built-in types by calling free(). However, there is no guarantee that future releases of Visual C++ will adhere to this convention. Furthermore, there’s no guarantees that this code will work on other compilers. To conclude, using delete instead of delete[] and vice versa is hazardous and should be avoided.

Nine to 11: Performance Enhancements
The following tips list three simple—yet rather unfamiliar—techniques to improve your program’s performance, without sacrificing its readability or entailing design modifications. For example, programmers often don’t know that simply by reordering data members in a class, they can significantly reduce its size. This optimization can boost performance especially if your application uses arrays of such objects. We will also learn the difference between postfix and prefix operatorsan issue of special importance when dealing with overloaded operators. Finally, we will learn a few techniques to eliminate the creation of temporary objects.

Tip 9: Optimizing Class Member Alignment
The size of a class can be changed simply by playing with the order of its members’ declaration:

struct A{ bool a; int b; bool c;}; /*sizeof (A) == 12*/

On my machine, sizeof (A) equals 12. This result might seem surprising because the total size of A’s members is only 6 bytes: 1+4+1 bytes. Where did the remaining 6 bytes come from? The compiler inserted 3 padding bytes after each bool member to make it align on a four-byte boundary. You can reduce A’s size by reorganizing its data members as follows:

struct B{ bool a; bool c; int b;}; // sizeof (B) == 8

This time, the compiler inserted only 2 padding bytes after the member c. Because b occupies four bytes, it naturally aligns on a word boundary without necessitating additional padding bytes.

Tip 10: Differences between Postfix and Prefix Operators
The built-in ++ and operators can appear on both sides of their operand:

int n=0;++n; /*prefix*/n++; /*postfix*/

You probably know that a prefix operator first changes its operand before taking its value. For example:

int n=0, m=0;n = ++m; /*first increment m, then assign its value to n*/cout << n << m; /* display 1 1*/

In this example, n equals 1 after the assignment because the increment operation took place before m’s value was taken and assigned to n. By contrast,

int n=0, m=0;n = m++; /*first assign m’s value to n, then increment m*/cout << n << m; /*display 0 1*/

In this example, n equals 0 after the assignment because the increment operation took place after m’s original value was taken and assigned to n.

To understand the difference between postfix and prefix operators better, examine the disassembly code generated for these operations. Even if you’re not familiar with assembly languages, you can immediately see the difference between the two; simply notice where the inc (increment) assembly directive appears:

/*disassembly of the expression: m=n++;*/mov ecx, [ebp-0x04] /*store n’s value in ecx register*/mov [ebp-0x08], ecx /*assign value in ecx to m*/inc dword ptr [ebp-0x04] /*increment n*//*disassembly of the expression: m=++n;*/inc dword ptr [ebp-0x04] /*increment n;*/mov eax, [ebp-0x04] /*store n’s value in eax register*/mov [ebp-0x08], eax /*assign value in eax to m*/

Tip 11: Eliminating Temporary Objects
C++ creates temporary objects “behind your back” in several contexts. The overhead of a temporary can be significant because both its constructor and destructor are invoked. You can prevent the creation of a temporary object in most cases, though. In the following example, a temporary is created:

Complex x, y, z;x=y+z; /* temporary created */

The expression y+z; results in a temporary object of type Complex that stores the result of the addition. The temporary is then assigned to x and destroyed subsequently. The generation of the temporary object can be avoided in two ways:

Complex y,z;Complex x=y+z; /* initialization instead of assignment */

In the example above, the result of adding x and z is constructed directly into the object x, thereby eliminating the intermediary temporary. Alternatively, you can use += instead of + to get the same effect:

/* instead of x = y+z; */x=y;  x+=z;

Although the += version is less elegant, it costs only two member function calls: assignment operator and operator +=. In contrast, the use of + results in three member function calls: a constructor call for the temporary, a copy constructor call for x, and a destructor call for the temporary.

12 and 13: Object-oriented Design
Although C++ supports several useful programming paradigms such as procedural programming, functional programming, and generic programming, object-oriented programming is unquestionably the most widely used and important paradigm. The following two tips provide guidelines for better object-oriented design and implementation. First, I will explain the importance of virtual destructors in class hierarchies. The next tip in this category shows how to deal with nested classes that are declared as friends of the enclosing class.

Tip 12: Why Inheriting from a Class That Has No Virtual Destructor is Dangerous
Classes with a non-virtual destructor aren’t meant to serve as base classes (such classes are usually known as “concrete classes”). std::string, std::complex, and std::vector are concrete classes. Why is inheriting from such classes not recommended? When you use public inheritance, you create an is-a relationship between the base class and its derived classes. Consequently, pointers and references to base can actually point to a derived object. Because the destructor isn’t virtual, C++ will not call the entire destructor chain when you delete such an object. For example:

class A{public:  ~A() // non virtual  {  // …  }};class B: public A /* bad; A has a non virtual dtor*/{public:  ~B()  {  // …  }};int main(){ A * p = new B; /*seemingly OK*/ delete p; /*trouble, B’s dtor not called*/}

The result of failing to invoke an object’s destructor is undefined. Therefore, you shouldn’t use publicly inherit from such classes. In particular, don’t derive from STL containers and std::string, as tempting as it may seem.

Tip 13: Declaring Nested Classes as Friends of Their Enclosing Class
When you declare a nested class as a friend of its containing class, place the friend declaration after the declaration of the nested class, not before it:

class A {private: int i;public: class B /*nested class declared first*/ {  public:  B(A & a) { a.i=0;}; /*access A’s private member*/ }; friend class B;/*friend declaration at the right place*/ };

If you place the friend declaration before the nested class’s declaration, the compiler will discard the declaration since the friend class hasn’t been seen yet.

14 to 20: STL and Generic Programming
The Standard Template Library (STL) has revolutionized the way C++ programmers write code. It brings code reuse to new levels of productivity and it saves huge amounts of time that would have otherwise been spent on reinventing wheels. Yet, it’s a full-blown framework, with a peculiar jargon and intricate rules that you have to master in order to learn how to use it effectively. To shed some light on certain aspects of STL, I decided to include no less than six tips under this category.

Naturally, the first tip describes the basic constituents of STL and some of its key terms.

The next tip deals with template definitions. As you probably know, templates are the building bricks of STL containers and algorithms. The following three tips describe techniques of using the std::vector container?the most widely-used STL container. We will learn how to properly store pointers to objects in a vector and how to avoid common pitfalls; we will see how to treat a vector object as if it were a built-in array. The fifth tip in this category will show you how to use vectors to imitate a multidimensional array. Finally, the closing tip in this category teaches a very important lesson about the interaction of std::auto_ptr and STL.

Tip 14: Useful STL Terminology
Here are some key terms that you may find useful when reading Standard Template Library (STL) literature and documentation.

Container
A container is an object that stores objects as its elements. Normally, it’s implemented as a class template that has member functions for traversing, storing and removing elements. Examples of container classes are std::list and std::vector.

Genericity
The quality of being generic, or type-independent. The above definition of the term container is too loose because it may apply to strings, arrays and structs, which also store objects. However, a real container isn’t limited to a specific data type or a set of types. Rather, it can store any built-in and user-defined type. Such a container is said to be generic. Note that a string can only contain characters. Genericity is perhaps the most important characteristic of STL. The third tip presents the standard base classes for function objects. Since function objects are one of the constituents of generic programming, adhering to standard conventions in their design and implementation will save you a lot of difficulties.

Algorithm
A set of operations applied to a sequence of objects. Examples of algorithms are std::sort(), std::copy(), and std::remove(). STL algorithms are implemented as function templates taking iterators.

Adaptor
An adaptor is a special object that can be plugged to an exiting class or function to change its behavior. For example, by plugging a special adaptor to the std::sort() algorithm, you can control whether the sorting order is descending or ascending. STL defines several kinds of sequence adaptors, which transform a container to a different container with a more restricted interface. A stack, for instance, is often formed from queue<> and an adaptor that provides the necessary push() and pop() operations.

Big Oh Notation
A special notation used in performance measurements of algorithms. The STL specification imposes minimum performance limits on the operations of its algorithms and container operations. An implementation may offer better performance but not worse. The Big Oh notation enables you to evaluate the efficiency of algorithms and containers for a given operation and data structure. An algorithm such as std::find(), which traverses every element is a sequence (in the worst case scenario) has the following notation:

T(n) = O(n). /* linear complexity */

Iterator
An iterator is an object that functions as a generic pointer. Iterators are used for traversing, adding and removing container elements. STL defines five major categories of iterators:

input iterators and output iterators
forward iterators
bidirectional iterators
random access iterators

Note that this list doesn’t represent inheritance relationships; it merely describes the iterator categories and their interfaces. Each lower category is a superset of the category above it. For instance, a bidirectional iterator provides all the functionality of a forward iterators plus additional functionality. Here is a brief summary of the functionality and interfaces for these categories:

Input iterators allow an algorithm to advance the iterator and offer read-only access to an element.

Output iterators allow an algorithm to advance the iterator and offer write-only access to an element.

Forward iterators support both read and write access, but traversal is permitted only in one direction.

Bidirectional iterators allow the user to traverse the sequence in both directions.

Random access iterators support random jumps and “pointer arithmetic” operations, for example:

string::iterator it = s.begin();char c = *(it+5); /* assign sixth char to c*/

Tip 15: The Location of Template Definitions
Normally, you declare functions and classes in a .h file and place their definition in a separate .cpp file. With templates, this practice isn’t really useful because the compiler must see the actual definition (i.e., the body) of a template, not just its declaration, when it instantiates a template. Therefore, it’s best to place both the template’s declaration and definition in the same .h file. This is why all STL header files contain template definitions.

In the future, when compilers support the “export” keyword, it will be possible to use only the template’s declaration and leave the definition in a separate source file.

Tip 16: Standard Base Classes for Function Object
To simplify the process of writing function objects, the Standard Library provides two class templates that serve as base classes of user-defined function objects: std::unary_function and std::binary_function. Both are declared in the header . As the names suggest, unary_function serves as a base class of function objects taking one argument and binary_function serves as a base class of function objects taking two arguments. The definitions of these base classes are as follows:

template < class Arg, class Res > struct unary_function { typedef Arg argument_type; typedef Res result_type;};template < class Arg, class Arg2, class Res > struct binary_function { typedef Arg first_argument_type; typedef Arg2 second_argument_type; typedef Res result_type;};

These templates don’t provide any useful functionality. They merely ensure that arguments and return values of their derived function objects have uniform names. In the following example, the predicate is_vowel, which takes one argument, inherits from unary_function:

template < class T > class is_vowel: public unary_function< T, bool >{public: bool operator ()(T t) const {  if ((t==’a’)||(t==’e’)||(t==’i’)||(t==’o’)||(t==’u’))   return true;  return false; }};

Tip 17: Storing Dynamically Allocated Objects in STL Containers
Suppose you need to store objects of different types in the same container. Usually, you do this by storing pointers to dynamically allocated objects. However, instead of using named pointers, insert the elements to the container as follows:

class Base {};class Derived : public Base{};std::vector  v;v.push_back(new Derived);v.push_back(new Base);

This way you ensure that the stored objects can only be accessed through their container. Remember to delete the allocated objects as follows:

delete v[0];delete v[1];

Tip 18: Treating a Vector as an Array
Suppose you have a vector of int and function that takes int *. To obtain the address of the internal array of the vector v and pass it to the function, use the expressions &v[0] or &*v.front(). For example:

void func(const int arr[], size_t length );int main(){ vector  vi; //.. fill vi func(&vi[0], vi.size());}

It’s safe to use &vi[0] and &*v.front() as the internal array’s address as long as you adhere to the following rules: First, func() shouldn’t access out-of-range array elements. Second, the elements inside the vector must be contiguous. Although the C++ Standard doesn’t guarantee that yet, I’m not aware of any implementation that doesn’t use contiguous memory for vectors. Furthermore, this loophole in the C++ Standard will be fixed soon.

Tip 19: Dynamic Multidimensional Arrays and Vectors
You can allocate multidimensional arrays manually, as in:

int (*ppi) [5]=new int[4][5]; /*parentheses required*//*fill array..*/ppi[0][0] = 65;ppi[0][1] = 66;ppi[0][2] = 67;//..delete [] ppi;

However, this style is tedious and error prone. You must parenthesize ppi to ensure that the compiler parses the declaration correctly, and you must delete the allocated memory. Worse yet, you can easily bump into buffer overflows. Using a vector of vectors to simulate a multidimensional array is a significantly superior alternative:

#include #include using namespace std;int main(){ vector  > v; /*two dimensions*/ v.push_back(vector ()); /*create v[0]*/ v.push_back(vector ()); /*create v[1]*/ v[0].push_back(15); /*assign v[0][0]*/ v[1].push_back(16); /*assign v[1][0]*/}

Because vector overloads operator [], you can use the [][] notation as if you were using a built-in two-dimensional array:

cout << v[0][0]; cout << v[1][0];  

The main advantages of using a vector of vectors are two: vector automatically allocates memory as needed. Secondly, it takes care of deallocating memory so you don’t have to worry about potential memory leaks.

Tip 20: Why You Shouldn’t Store auto_ptr Objects in STL Containers
The C++ Standard says that an STL element must be “copy-constructible” and “assignable.” These fancy terms basically mean that for a given class, assigning and copying one object to another are well-behaved operations. In particular, the state of the original object isn’t changed when you copy it to the target object.

This is not the case with auto_ptr, though: copying or assigning one auto_ptr to another makes changes to the original in addition to the expected changes in the copy. To be more specific, the original object transfers ownership of the pointer to the target, thus making the pointer in the original null. Imagine what would happen if you did something like this:

std::vector  > vf;/*a vector of auto_ptr’s*/// ..fill vfint g(){  std::auto_ptr  temp=vf[0]; /*vf[0] becomes null*/}

When temp is initialized, the pointer of vf[0] becomes null. Any attempt to use that element will cause a runtime crash. This situation is likely to occur whenever you copy an element from the container. Remember that even if your code doesn’t perform any explicit copy or assignment operations, many algorithms (std::swap(), std::random_shuffle() etc.) create a temporary copy of one or more container elements. Furthermore, certain member functions of the container create a temporary copy of one or more elements, thereby nullifying them. Any subsequent attempt to the container elements is therefore undefined.

Visual C++ users often say that they have never encountered any problems with using auto_ptr in STL containers. This is because the auto_ptr implementation of Visual C++ (all versions thereof) is outdated and relies on an obsolete specification. When the vendor decides to catch up with the current ANSI/ISO C++ Standard and change its Standard Library accordingly, code that uses auto_ptr in STL containers will manifest serious malfunctions.

To conclude, you shouldn’t use auto_ptr in STL containers. Use either bare pointers or other smart pointer classes instead of auto_ptr (such classes are available at www.Boost.org).

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist