< Supplementary Note 1 | Main | C++ .NET Exception Handling 1 >


 

 

Supplementary Notes 2

Let Have A Break In Playing With Visual C++ .Net!

 

 

  1. General Rules for Operator Overloading

  2. Example

  3. Implicit Boxing

  4. Example

  5. Explicitly Request Boxing

  6. Use gcnew to Create Value Types and Use Implicit Boxing

  7. Unboxing

  8. Standard Conversions and Implicit Boxing

  9. static Keyword

  10. Static Member Functions

General Rules for Operator Overloading

 

The following rules constrain how overloaded operators are implemented. However, they do not apply to the new and delete operators, which are covered separately.

  1. You cannot define new operators, such as **.

  2. You cannot redefine the meaning of operators when applied to built-in data types.

  3. Overloaded operators must either be a non-static class member function or a global function. A global function that needs access to private or protected class members must be declared as a friend of that class. A global function must take at least one argument that is of class or enumerated type or that is a reference to a class or enumerated type. For example:

// rules for operator overloading

// compile with: /clr

#include "stdafx.h"

 

using namespace System;

 

class Point

{

   public:

     // Declare a member operator overload.

     Point operator<(Point &);

     // Declare addition operators.

     friend Point operator+(Point&, int);

     friend Point operator+(int, Point&);

};

 

int main()

{

   return 0;

}

The preceding code sample declares the less-than operator as a member function; however, the addition operators are declared as global functions that have friend access. Note that more than one implementation can be provided for a given operator. In the case of the preceding addition operator, the two implementations are provided to facilitate commutativity. It is just as likely that operators that add a Point to a Point, int to a Point, and so on, might be implemented.

Operators obey the precedence, grouping, and number of operands dictated by their typical use with built-in types. Therefore, there is no way to express the concept "add 2 and 3 to an object of type Point," expecting 2 to be added to the x coordinate and 3 to be added to the y coordinate. Unary operators declared as member functions take no arguments; if declared as global functions, they take one argument. Binary operators declared as member functions take one argument; if declared as global functions, they take two arguments. If an operator can be used as either a unary or a binary operator (&, *, +, and -), you can overload each use separately. Overloaded operators cannot have default arguments. All overloaded operators except assignment (operator=) are inherited by derived classes. The first argument for member-function overloaded operators is always of the class type of the object for which the operator is invoked (the class in which the operator is declared, or a class derived from that class). No conversions are supplied for the first argument. Note that the meaning of any of the operators can be changed completely. That includes the meaning of the address-of (&), assignment (=), and function-call operators. Also, identities that can be relied upon for built-in types can be changed using operator overloading. For example, the following four statements are usually equivalent when completely evaluated:

var = var + 1;

var += 1;

var++;

++var;

This identity cannot be relied upon for class types that overload operators. Moreover, some of the requirements implicit in the use of these operators for basic types are relaxed for overloaded operators. For example, the addition/assignment operator, +=, requires the left operand to be an l-value when applied to basic types; there is no such requirement when the operator is overloaded. For consistency, it is often best to follow the model of the built-in types when defining overloaded operators. If the semantics of an overloaded operator differ significantly from its meaning in other contexts, it can be more confusing than useful.

 

Boxing: Boxed and Unboxed, The __box keyword

 

This topic applies only to version 1 of Managed Extensions for C++. This syntax should only be used to maintain version 1 code. Use implicit boxing for the equivalent functionality in the new syntax, explained in the following sections or if compiled using VC++ 2005, use the /clr:OldSyntax option. The __box keyword creates a managed copy of a __value class object. The syntax is:

__box(value-class identifier)

The __box keyword is used to create a managed object (derived from System::ValueType) from an existing __value class object. When the __box keyword is applied to a __value class:

  1. A managed object is allocated on the common language runtime heap.

  2. The current value of the __value class object is bit-wise copied into the managed object.

  3. The address of the new managed object is returned.

This process is known as boxing. This enables any __value class object to be used in generic routines that work for any managed object because the managed object indirectly inherits from System::Object (since System::ValueType inherits from System::Object). The newly created boxed object is a copy of the __value class object. Therefore, modifications to the value of the boxed object do not affect the contents of the __value class object.

 

Example

 

Here's is a program example that does boxing and unboxing.

// __box keyword

// compile with: /clr:oldSyntax

#include "stdafx.h"

 

using namespace System;

 

int main()

{

  Int32 i = 100;

  // boxing, so the value type become an object...

  System::Object* obj = __box(i);

  Console::WriteLine("obj = {0}", obj);

  // unboxing...

  Int32 j = *dynamic_cast<__box Int32*>(obj);

  Console::Write("j = ");

  Console::WriteLine(j);

}

 

Output:

A program example that does boxing and unboxing

 

In the following example, an unmanaged value type (Val) is boxed and passed as a managed parameter to the Positive function.

// __box keyword

// compile with: /clr:oldSyntax

#include "stdafx.h"

 

using namespace System;

 

__value struct Val

{

   int i;

};

 

// expects a managed class

void Positive(Object*) {}

 

int main()

{

   // allocate and initialize

   Val myval = {100};

   Console::Write("v.i = ");

   Console::WriteLine(myval.i);

   // copy to the common language runtime heap

   __box Val* pBoxedV = __box(myval);

   // treat as a managed class

   Positive(pBoxedV);

   // update the boxed version

   pBoxedV->i = 200;

   Console::Write("pBoxedV->i = ");

   Console::WriteLine(pBoxedV->i);

}

 

Output:

An example, an unmanaged value type (Val) is boxed and passed as a managed parameter to the Positive function

 

Implicit Boxing

 

The Visual C++ compiler now boxes value types to Object. This is possible because of a compiler-defined conversion to convert value types to Object. Boxing and unboxing enable value types to be treated as objects. Value types, including both struct types and built-in types such as int, can be converted to and from the type Object.

 

Example

 

The following code example shows how explicit boxing was done with Managed Extensions for C++.

// explicit boxing

// compile with: /clr:oldSyntax

#include "stdafx.h"

 

using namespace System;

 

__gc class myClass

{

   public:

        void func(System::Object * o){Console::WriteLine("In myClass...");}

};

 

__value class ValClass {};

 

__gc __interface IFace

{

   void func();

};

 

__value class ValClass1 : public IFace

{

   public:

        void func() { Console::WriteLine("In Interface function...");  }

};

 

int main()

{

   // example 1 explicit boxing, Int32 to object...

   Int32 __box* bxim = __box(100);

   Console::Write("bxim = ");

   Console::WriteLine(bxim);

   // example 2 calling a member with explicit boxing

   Int32 myint = 200;

   // Int32 to string object...

   Console::WriteLine(S"myint = {0}", __box(myint)->ToString());

   // example 3 explicit boxing for function calls

   myClass * myfunctcall = new myClass;

   // using an object as function argument...

   myfunctcall->func(__box(myint));

   // example 4  explicit boxing for WriteLine function call

   ValClass val;

   // another argument as an object...

   Console::WriteLine(S"Class {0} passed using implicit boxing", __box(val)->ToString());

   // example 5 casting to a base with explicit boxing

   ValClass1 val1;

   // casting with an object...

   IFace * iface = __box(val1);

   iface->func();

   return 0;

}

 

Output:

A sample shows how explicit boxing was done with Managed Extensions for C++

 

The following sample code shows how implicit boxing works.

// explicit boxing

// compile with: /clr

#include "stdafx.h"

 

using namespace System;

 

ref class myClass

{

   public:

        void func(System::Object^ o){Console::WriteLine("In myClass...");}

};

 

value class ValClass {};

 

interface struct IFace

{

   void func();

};

 

value class ValClass1 : public IFace

{

   public:

        virtual void func() { Console::WriteLine("In Interface function..."); }

};

 

value struct ValStruct2

{

   // conversion operator to System::Object

   static operator System::Object^(ValStruct2 val2)

   {

       Console::WriteLine("operator System::Object^");

       return (ValStruct2^)val2;

   }

};

 

void func1(System::Object^){Console::WriteLine("In void func1(System::Object^)...");}

void func1(ValStruct2^){Console::WriteLine("In func1(ValStruct2^)...");}

void func2(System::ValueType^){Console::WriteLine("In func2(System::ValueType^)...");}

void func2(System::Object^){Console::WriteLine("In func2(System::Object^)...");}

 

int main()

{

   // example 1 simple implicit boxing

   Int32^ bi = 100;

   Console::Write("bi = ");

   Console::WriteLine(bi);

   // example 2 calling a member with implicit boxing

   Int32 n = 10;

   Console::WriteLine("n.ToString() = {0}", n.ToString());

   // example 3 implicit boxing for function calls

   myClass^ a = gcnew myClass;

   a->func(n);

   // example 4 implicit boxing for WriteLine function call

   ValClass val;

   Console::WriteLine("Class {0} passed using implicit boxing", val);

   // force boxing

   Console::WriteLine("Class {0} passed with forced boxing", (ValClass^)(val));

   // example 5 casting to a base with implicit boxing

   ValClass1 val1;

   IFace ^ iface = val1;

   iface->func();

   // example 6 user-defined conversion preferred over

   // implicit boxing for function-call parameter matching

   ValStruct2 val2;

   // user defined conversion from ValStruct2 to

   // System::Object preferred over implicit boxing

   // Will call void func1(System::Object^);

   func1(val2);

   // OK: Calls "static val2::operator System::Object^(ValStruct2 val2)"

   func2(val2);

   // Using explicit boxing: calls func2(System::ValueType^)

   func2((ValStruct2^)val2);

 

   return 0;

}

 

Output:

 

 

 

 

 

A sample shows how implicit boxing works

 

Explicitly Request Boxing

 

You can explicitly request boxing by assigning a variable to a variable of type Object. The following is an example.

// explicit boxing

// compile with: /clr

#include "stdafx.h"

 

using namespace System;

 

void funct(int i)

{

   Console::WriteLine("In funct(int i)");

}

 

void funct(Object ^obj)

{

   Console::WriteLine("In funct(Object^ obj)");

}

 

int main()

{

   double i = 22.22;

   i = 323.5654;

   // forces i to be boxed

   Console::WriteLine("Forcing the value type to an object, warning generated...");

   Object ^ Obj = i;

   funct(i);

   // boxes i

   Console::WriteLine("Boxing the value type to an object...");

   funct((Object^)i);

}

 

Output:

Explicitly request boxing by assigning a variable to a variable of type Object

 

Use gcnew to Create Value Types and Use Implicit Boxing

 

Using gcnew on a value type will create a boxed value type, which can then be placed on the managed, garbage-collected heap. The following is a program example.

// More on the explicit boxing

// compile with: /clr

#include "stdafx.h"

 

using namespace System;

 

public value class myValClass

{

   public:

      int myint;

      myValClass(int i) : myint(i)

      {

         Console::WriteLine("In myValClass...");

         Console::WriteLine("i = {0}", i);

      }

};

 

public ref struct myRefStruct

{

   void do_some_test(myValClass^ val)

   {

     if (val != nullptr)

        Console::WriteLine("val is not a null pointer");

     else

        Console::WriteLine("val is a null pointer");

   }

};

 

int main()

{

   myValClass^ val = gcnew myValClass(100);

   myRefStruct^ refstruct = gcnew myRefStruct;

   refstruct->do_some_test(val);

}

 

Output:

 

 

 

 

 

 

 

---------------------------------------------

 

Unboxing

 

The following example shows how to unbox and modify a value.

// unboxing

// compile with: /clr

#include "stdafx.h"

 

using namespace System;

 

int main()

{

   int ^ i = gcnew int(100);

   int j;

 

   Console::WriteLine("An object of an integer...");

   Console::Write("i = ");

   // unboxing

   Console::WriteLine(*i);

   // unboxing and assignment

   *i = 300;

   Console::WriteLine("An object to an integer...");

   Console::Write("i = ");

   Console::WriteLine(*i);

   // unboxing and assignment

   j = safe_cast<int>(i);

   Console::WriteLine("Safe cast an object to an integer...");

   Console::Write("j = safe_cast<int>(i) = ");

   Console::WriteLine(j);

   return 0;

}

 

Output:

An example shows how to unbox and modify a value

 

Standard Conversions and Implicit Boxing

 

A standard conversion will be chosen by the compiler over a conversion that requires boxing. The following is a program example.

// clr, implicit boxing and standard conversion

// compile with: /clr

#include "stdafx.h"

 

using namespace System;

 

// we have 2 functions with same name...

int funct3(int ^ i)

{

   // requires boxing

   Console::WriteLine("In funct3() with argument pointing to the heap...");

   return 1;

}

 

int funct3(char c)

{

   // no boxing required, standard conversion

   Console::WriteLine("In funct3() with normal argument on the stack...");

   return 2;

}

 

int main()

{

   int i = 5;

   Console::WriteLine("Return value = {0}", funct3(i));

}

 

Output:

A standard conversion will be chosen by the compiler over a conversion that requires boxing

 

static Keyword

 

When modifying a variable, the static keyword specifies that the variable has static duration (it is allocated when the program begins and de-allocated when the program ends) and initializes it to 0 unless another value is specified. When modifying a variable or function at file scope, the static keyword specifies that the variable or function has internal linkage (its name is not visible from outside the file in which it is declared). A variable declared static in a function retains its state between calls to that function. When modifying a data member in a class declaration, the static keyword specifies that one copy of the member is shared by all instances of the class. When modifying a member function in a class declaration, the static keyword specifies that the function accesses only static members. Static data members of classes must be initialized at file scope.

In recursive code, a static object or variable is guaranteed to have the same state in different instances of a block of code. The members of a union cannot be declared as static. An anonymous union declared globally must be explicitly declared static. Objects and variables defined outside all blocks have static lifetime and external linkage by default. A global object or variable that is explicitly declared as static has internal linkage. The following example shows how a variable declared static in a function retains its state between calls to that function.

// static keyword

// compile with: /clr

#include "stdafx.h"

#include <iostream>

 

using namespace std;

 

void showstat(int curr)

{

   // Value of nStatic is retained between each function call

   static int nStatic;

   nStatic++;

   cout<<"nStatic pass #"<<nStatic<<endl;

}

 

int main()

{

   for (int i = 0; i < 7; i++)

     showstat(i);

   return 0;

}

 

Output:

An example shows how a variable declared static in a function retains its state between calls to that function

 

When the static keyword is removed the output is:

 

An example shows how a variable declared static in a function retains its state between calls to that function - removing the static keyword to see the effect

 

The following example shows the use of static in a class.

// static and class

// compile with: /clr

#include "stdafx.h"

#include <iostream>

 

using namespace std;

 

class CMyClass

{

   public:

    static int memberdata_i;

};

 

int CMyClass::memberdata_i = 100;

 

int main()

{

   cout<<"First assignment, memberdata_i = 100"<<endl;

   cout<<"Pass #1 = "<<CMyClass::memberdata_i<<endl;

   cout<<"Pass #2 = "<<CMyClass::memberdata_i<<endl;

   CMyClass::memberdata_i = 200;

   cout<<"Second assignment, memberdata_i = 200"<<endl;

   cout<<"Pass #3 = "<<CMyClass::memberdata_i<<endl;

   cout<<"Pass #4 = "<<CMyClass::memberdata_i<<endl;

   return 0;

}

 

Output:

An example shows the use of static in a class

 

The following example shows a local variable declared static in a member function. The static variable is available to the whole program; all instances of the type share the same copy of the static variable. Assigning to a static local variable is not thread safe and is not recommended as a programming practice.

// static keyword, local variable and member function

// compile with: /clr

#include "stdafx.h"

#include <iostream>

 

using namespace std;

 

struct myStruct

{

   void Test(int value)

   {

     static int var = 0;

     if (var == value)

       cout<<"var == value"<<endl;

     else

       cout<<"var != value"<<endl;

     var = value;

   }

};

 

int main()

{

    myStruct val1;

    myStruct val2;

    val1.Test(100);

    val2.Test(100);

    return 0;

}

 

Output:

An example shows a local variable declared static in a member function

 

When the static keyword is removed the output is:

 

example shows a local variable declared static in a member function - removing the static keyword

 

Static Member Functions

 

Static member functions are considered to have class scope. In contrast to non-static member functions, these functions have no implicit this argument; therefore, they can use only static data members, enumerators, or nested types directly. Static member functions can be accessed without using an object of the corresponding class type. Consider this example:

// static member functions

// compile with: /clr

#include "stdafx.h"

#include <stdio.h>

 

class StaticTest

{

   private:

      static int x;

   public:

      static int count()

       { return x; }

};

 

int StaticTest::x = 10;

 

int main()

{

    printf_s("x = %d\n", StaticTest::count());

}

 

Output:

The class StaticTest contains the static member function count

 

In the preceding code, the class StaticTest contains the static member function count. This function returns the value of the private class member but is not necessarily associated with a given object of type StaticTest. Static member functions have external linkage. These functions do not have this pointers. As a result, the following restrictions apply to such functions:

  1. They cannot access non-static class member data using the member-selection operators (. or –>).

  2. They cannot be declared as virtual.

  3. They cannot have the same name as a non-static function that has the same argument types.

The left side of a member-selection operator (. or –>) that selects a static member function is not evaluated. This can be important if the function is used for its side effects. For example, the expression SideEffects().CountOf() does not call the function SideEffects.

 

 

 

 

Part 1 | part 2

 


 

< Supplementary Note 1 | Main | C++ .NET Exception Handling 1 >