Assignment Operator C++ Dynamic Array Initialization

The latest version of this topic can be found at new Operator (C++).

Allocates memory for an object or array of objects of type-name from the free store and returns a suitably typed, nonzero pointer to the object.

Microsoft C++ Component Extensions provides support for the keyword to add vtable slot entries. For more information, see new (new slot in vtable)

If unsuccessful, new returns zero or throws an exception; see The new and delete Operators for more information. You can change this default behavior by writing a custom exception-handling routine and calling the _set_new_handler run-time library function with your function name as its argument.

For information on how to create an object on the managed heap, see gcnew.

When new is used to allocate memory for a C++ class object, the object's constructor is called after the memory is allocated.

Use the delete operator to deallocate the memory allocated with the new operator.

The following example allocates and then frees a two-dimensional array of characters of size by 10. When allocating a multidimensional array, all dimensions except the first must be constant expressions that evaluate to positive values; the leftmost array dimension can be any expression that evaluates to a positive value. When allocating an array using the new operator, the first dimension can be zero — the new operator returns a unique pointer.

The type-name cannot contain const, , class declarations, or enumeration declarations. Therefore, the following expression is illegal:

The new operator does not allocate reference types because they are not objects.

The new operator cannot be used to allocate a function, but it can be used to allocate pointers to functions. The following example allocates and then frees an array of seven pointers to functions that return integers.

If you use the operator new without any extra arguments, and compile with the /GX, /EHa, or /EHs option, the compiler will generate code to call operator delete if the constructor throws an exception.

The following list describes the grammar elements of new:

placement
Provides a way of passing additional arguments if you overload new.

type-name
Specifies type to be allocated; it can be either a built-in or user-defined type. If the type specification is complicated, it can be surrounded by parentheses to force the order of binding.

initializer
Provides a value for the initialized object. Initializers cannot be specified for arrays. The new operator will create arrays of objects only if the class has a default constructor.

The following code example allocates a character array and an object of class and then frees them.

If you use the placement new form of the new operator, the form with arguments in addition to the size of the allocation, the compiler does not support a placement form of the delete operator if the constructor throws an exception. For example:

An optional initializer field is included in the grammar for the new operator. This allows new objects to be initialized with user-defined constructors. For more information about how initialization is done, see Initializers. The following example illustrates how to use an initialization expression with the new operator:

In this example, the object is allocated using the new operator, but no default initialization is specified. Therefore, the default constructor for the class, , is called. Then the object is allocated the same way, except that it is explicitly initialized to 34.98. Because 34.98 is of type double, the constructor that takes an argument of that type is called to handle the initialization. Finally, the nonclass type is initialized to 43.0.

If an object is of a class type and that class has constructors (as in the preceding example), the object can be initialized by the new operator only if one of these conditions is met:

  • The arguments provided in the initializer agree with those of a constructor.

  • The class has a default constructor (a constructor that can be called with no arguments).

Access control and ambiguity control are performed on and on the constructors according to the rules set forth in Ambiguity and Initialization Using Special Member Functions.

No explicit per-element initialization can be done when allocating arrays using the new operator; only the default constructor, if present, is called. See Default Arguments for more information.

If the memory allocation fails ( returns a value of 0), no initialization is performed. This protects against attempts to initialize data that does not exist.

As with function calls, the order in which initialized expressions are evaluated is not defined. Furthermore, you should not rely on these expressions being completely evaluated before the memory allocation is performed. If the memory allocation fails and the new operator returns zero, some expressions in the initializer may not be completely evaluated.

Objects allocated with the new operator are not destroyed when the scope in which they are defined is exited. Because the new operator returns a pointer to the objects it allocates, the program must define a pointer with suitable scope to access those objects. For example:

Once the pointer goes out of scope in the example, the object can no longer be deleted.

The allocation-expression — the expression containing the new operator — does three things:

  • Locates and reserves storage for the object or objects to be allocated. When this stage is complete, the correct amount of storage is allocated, but it is not yet an object.

  • Initializes the object(s). Once initialization is complete, enough information is present for the allocated storage to be an object.

  • Returns a pointer to the object(s) of a pointer type derived from new-type-name or type-name. The program uses this pointer to access the newly allocated object.

The new operator invokes the function . For arrays of any type, and for objects that are not of class, , or union types, a global function, ::operator new, is called to allocate storage. Class-type objects can define their own static member function on a per-class basis.

When the compiler encounters the new operator to allocate an object of type , it issues a call to ::operator new( sizeof() ) or, if no user-defined is defined, ::operator new( sizeof() ). Therefore, the new operator can allocate the correct amount of memory for the object.

The argument to is of type size_t. This type is defined in DIRECT.H, MALLOC.H, MEMORY.H, SEARCH.H, STDDEF.H, STDIO.H, STDLIB.H, STRING.H, and TIME.H.

An option in the grammar allows specification of placement (see the Grammar for new Operator). The placement parameter can be used only for user-defined implementations of ; it allows extra information to be passed to . An expression with a placement field such as is translated to if class T has member operator new, otherwise to .

The original intention of the placement field was to allow hardware-dependent objects to be allocated at user-specified addresses.

Although the preceding example shows only one argument in the placement field, there is no restriction on how many extra arguments can be passed to this way.

Even when has been defined for a class type, the global operator can be used by using the form of this example:

The scope-resolution operator () forces use of the global new operator.

Expressions with Unary Operators
Keywords
operator new Function

[::] new [placement] new-type-name [new-initializer] [::] new [placement] ( type-name ) [new-initializer]
char (*pchar)[10] = new char[dim][10]; delete [] pchar;
volatile char *vch = new volatile char[20];
int (**p) () = new (int (*[7]) ()); delete *p;
// expre_new_Operator.cpp // compile with: /EHsc #include <string.h> class CName { public: enum { sizeOfBuffer = 256 }; char m_szFirst[sizeOfBuffer]; char m_szLast[sizeOfBuffer]; public: void SetName(char* pszFirst, char* pszLast) { strcpy_s(m_szFirst, sizeOfBuffer, pszFirst); strcpy_s(m_szLast, sizeOfBuffer, pszLast); } }; int main() { // Allocate memory for the array char* pCharArray = new char[CName::sizeOfBuffer]; strcpy_s(pCharArray, CName::sizeOfBuffer, "Array of characters"); // Deallocate memory for the array delete [] pCharArray; pCharArray = NULL; // Allocate memory for the object CName* pName = new CName; pName->SetName("Firstname", "Lastname"); // Deallocate memory for the object delete pName; pName = NULL; }
// expre_new_Operator2.cpp // C2660 expected class A { public: A(int) { throw "Fail!"; } }; void F(void) { try { // heap memory pointed to by pa1 will be deallocated // by calling ::operator delete(void*). A* pa1 = new A(10); } catch (...) { } try { // This will call ::operator new(size_t, char*, int). // When A::A(int) does a throw, we should call // ::operator delete(void*, char*, int) to deallocate // the memory pointed to by pa2. Since // ::operator delete(void*, char*, int) has not been implemented, // memory will be leaked when the deallocation cannot occur. A* pa2 = new(__FILE__, __LINE__) A(20); } catch (...) { } } int main() { A a; }
// expre_Initializing_Objects_Allocated_with_new.cpp class Acct { public: // Define default constructor and a constructor that accepts // an initial balance. Acct() { balance = 0.0; } Acct( double init_balance ) { balance = init_balance; } private: double balance; }; int main() { Acct *CheckingAcct = new Acct; Acct *SavingsAcct = new Acct ( 34.98 ); double *HowMuch = new double ( 43.0 ); // ... }
// expre_Lifetime_of_Objects_Allocated_with_new.cpp // C2541 expected int main() { // Use new operator to allocate an array of 20 characters. char *AnArray = new char[20]; for( int i = 0; i < 20; ++i ) { // On the first iteration of the loop, allocate // another array of 20 characters. if( i == 0 ) { char *AnotherArray = new char[20]; } } delete [] AnotherArray; // Error: pointer out of scope. delete [] AnArray; // OK: pointer still in scope. }
T *TObject =::new TObject;

Dynamic Allocation and Copy Constructors

Dr. Lawlor, CS 202, CS, UAF

(See Gaddis Chapter 9.8 for more info on new and delete, and Chapter 14.4 and 14.5 for info on copy constructors.)

So the basic dynamic allocation syntax is pretty simple: you call "new" to get a pointer, use the space, and finally call "delete" to give up that space.

The typical use is to allocate arrays at runtime:
  • "someptr=new int[n];" allocates space for an array of n integers, and returns you a pointer to the first integer.  Unlike just declaring an array, dynamic allocation with new allows "n" to be a runtime variable.
  • "delete[] someptr;" frees the array.  The "[]" looks like some sort of weird typo, but it indicates you're deleting the whole array.
You can also allocate a single object, although this isn't nearly as common:
  • "someptr=new int;" allocates space for one single integer, and returns you a pointer to the integer.
  • "delete someptr;" frees up the space again.  You really need to free any space you allocate, especially in a long-running program.
Note that for both array and individual allocations, you get a bare pointer back.  This is annoying, because it means you MUST remember whether you've got an array or an individual object, so you can access the space properly.

Here's an example of array allocation:
int foo(void) {
int n=5; cin>>n; // how many integers?
int *ptr=new int[n]; // make an array
for (int i=0;i<n;i++) // write data in
ptr[i]=10*i;
for (int i=0;i<n;i++) // read data back out
cout<<"ptr["<<i<<"]="<<ptr[i]<<"\n";
delete[] ptr; // free allocated space
return 0;
}

(Try this in NetRun now!)

Here's an example of allocating a single integer:
int foo(void) {
int *ptr=new int; // make a pointer
*ptr=7; // write data in
int retval=*ptr; // read data back out
delete ptr; // free allocated space
return retval;
}

(Try this in NetRun now!)

Unfortunately, there are a bunch of things you MUST do with dynamic allocations, and the compiler often can't detect any of these!
  • Setup: You MUST initialize your pointers before using them.  Luckily, the compiler can usually warn you about uninitialized pointers, and uninitialized pointers usually crash immediately.
  • Embezzlement: You MUST access your pointers within the array bounds.  If you asked for [10] elements, just reading from [13] might cause you to crash, or you might read garbage.  Writing is even worse--if you don't crash, you'll overwrite some other part of the program, which will then crash at some unknown later date.
  • Amnesia: You MUST remember to call delete.  If you don't call delete, memory marked as being in use will build up in your program (a "memory leak"), until the machine runs out of memory or your program exits.
  • Doppleganger: You MUST call the correct version of delete: "delete[]" for arrays, and plain "delete" for individual pointers.  Unfortunately, the compiler doesn't detect when you use the wrong delete; it just silently screws up memory so your program crashes sometime in the distant future.
  • Overkill: You MUST not call delete more than once on the same pointer.  You can protect against this by zeroing out your pointers after deleting them (like "delete[] someptr; someptr=0;").  This "double delete bug" is actually common enough that some machines' "delete" has explicit code to check for it.  But it's really hard to detect if you allocate some space and delete it, then somebody else allocates the same space and you then delete their space!
  • Zombies: You MUST not access a pointer after you've deleted it.  Unfortunately, these "living dead" pointers usually work, and some of your data is often still there, but of course that space could be reused by anybody else at any time, resulting in hideous weird crashes.
Because bare pointers are so error-prone, it's common to wrap them in a nicer class interface.

Building a "Wrapper Class" for Nicer Pointers

Here's a simple class that puts a slightly nicer interface onto an array of integers.  The constructor calls "new", there's an overloaded bracket operator to check accesses to the elements of the array, and the destructor calls "delete".
// A nice wrapper around a dynamically allocated array of integers.
class SmartArray {
private:
int *data;
int length;
public:
SmartArray(int len) {
data=new int[len]; // allocate space
length=len;
}
int &operator[](int index) {
if (index<0 || index>=length) { // bounds-check the array index
cout<<"Index "<<index<<" out of bounds!\n";
exit(1);
}
return data[index];
}
~SmartArray() {
delete[] data; // free space
data=0; /* zero out our pointer, to indicate we're gone */
length=-1;
}
};

int foo(void) {
int n=5; cin>>n;
SmartArray arr(n); // array has n integers
for (int i=0;i<n;i++) arr[i]=10*i;
for (int i=0;i<n;i++) cout<<"arr["<<i<<"] = "<<arr[i]<<"\n";
return 0; // destructor automatically deallocates array!
}

(Try this in NetRun now!)

The nice part about this is:
  • The array remembers how long it is, and checks to make sure each access is inside this range.
  • The destructor will be called automatically by the compiler, so you can't possibly forget it.
This is an example of a standard C++ trick called Resource Aquisition Is Initialization (RAII): the constructor allocates, and the destructor deallocates.

Copy Constructor and Assignment Operator

One problem with the above "SmartArray" class is that the C++ compiler automatically (and stupidly) allows people to make a simple shallow copy of a SmartArray object.  Unfortunately, the two copies share the same pointer, so the pointer will be deleted twice!  For example:
class SmartArray {
private:
int *data;
int length;
public:
SmartArray(int len) {
data=new int[len]; // allocate space
length=len;
cout<<"Running SmartArray constructor: data="<<data<<"\n";
}
~SmartArray() {
cout<<"Running SmartArray destructor: data="<<data<<"\n";
delete[] data; // free space
data=0; /* zero out our pointer, to indicate we're gone */
length=-1;
}
};

int foo(void) {
SmartArray arr(2); // array has 2 integers
if (2==2) { // make a copy of the array
cout<<"Making another SmartArray...\n";
SmartArray evilArr=arr; // compiler-generated assignment operator!
// evilArr's destructor will delete arr's pointer!
}
cout<<"Returning from foo...\n";
return 0;
// uh oh! arr's destructor calls delete *again*!
}

(Try this in NetRun now!)

There are two different ways the compiler might make a copy of "arr":
  • "SmartArray somebody(arr);" makes a new SmartArray as a copy of "arr"'s values.  This is called a "copy constructor".
  • "somebody = arr;" overwrites an existing SmartArray with an assignment of "arr"'s values.  This is called an "assignment operator".
Unfortunately, the compiler's automatically generated copy constructor and assignment operator WILL cause big problems in any class with pointers.  And people tend to copy and assign classes a lot, for example to pass them into a function or return them.  So you often need to write your own copy constructor and assignment operator.  This is known as the "Law of the Big Three": if you've got any one of a destructor, copy constructor, or assignment operator, then you probably need all three of them.

One weird trick is to declare a private copy constructor and assignment operator.  That way nobody can call them.  If nobody calls them, you don't even need a body for these functions:
class SmartArray {
private:
int *data;
int length;
// You can't copy or assign SmartArrays (yet)
SmartArray(const SmartArray &from); // copy constructor
SmartArray& operator=(const SmartArray &from); // assignment operator
public:
... rest of stuff ...
};

(Try this in NetRun now!)

This makes any attempt to copy or assign SmartArrays a compile error, which is way better than getting a horrible crash at runtime. 

If you're really ambitious, you can write the copy constructor and assignment operator to do the right thing, making a new copy of the class's data.  This is trickier than it sounds, especially if speed is important, or for the case where some joker assigns an instance to itself (self assignment, like "x=x;").   The question is, to implement "a=b;" for arrays, do you just copy the pointers, a "shallow copy" like the compiler does?  If so, how do you keep track of when to delete the array data?  (There are some cool implementations like "reference counting" and "garbage collection" out there.)  Or do you just copy all the data in the array?  This is called a "deep copy", which uses more time and space, but is a little easier to write.

Here, I've written a deep copy implementation, and added some new little utility methods called "alloc" and "free" to do the data allocation.
/*
A nice wrapper around a dynamically allocated array of integers.
*/
class SmartArray {
public:
SmartArray(int len) { alloc(len);} // ordinary constructor

SmartArray(const SmartArray &from) { // copy constructor
alloc(from.length);
for (int i=0;i<length;i++) data[i]=from[i];
}

SmartArray& operator=(const SmartArray &from) { // assignment operator
if (data==from.data) return *this; // self assignment!
free(); // throw away our old data
alloc(from.length);
for (int i=0;i<length;i++) data[i]=from[i];
return *this;
}

// Array indexing:
int &operator[](int index) { check(index); return data[index]; }

// Constant array indexing:
const int &operator[](int index) const { check(index); return data[index]; }

// Destructor
~SmartArray() { free(); }
private:
int *data;
int length;
void alloc(int len) {// allocate space for len bytes of data
data=new int[len];
length=len;
}
void free(void) {// deallocate data
delete[] data; // free space
data=0; /* zero out our pointer, to indicate we're really gone */
length=-1;
}
void check(int index) const {// bounds-check array index
if (index<0 || index>=length) { // bounds-check the array index
cout<<"Index "<<index<<" out of bounds!\n";
exit(1);
}
}
};

int foo(void) {
int n=5; cin>>n;
SmartArray arr(n); // array has n integers
for (int i=0;i<n;i++) arr[i]=10*i;
if (2==2) { // make a copy of the array
cout<<"Making another SmartArray...\n";
SmartArray evilArr=arr; // our own assignment operator (OK)
// evilArr's destructor will delete its own separate pointer
}
for (int i=0;i<n;i++) cout<<"arr["<<i<<"] = "<<arr[i]<<"\n";
cout<<"Returning from foo...\n";
return 0;
}

(Try this in NetRun now!)

Clearly, this is not something you want to write very often.  So you should re-use something like "SmartArray" rather than writing it from scratch each time!

0 thoughts on “Assignment Operator C++ Dynamic Array Initialization

Leave a Reply

Your email address will not be published. Required fields are marked *