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.
|
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:
A managed object is allocated on the common language runtime heap.
The current value of the __value class object is bit-wise copied into the managed object.
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:

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:

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:

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:

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:

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:

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:

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:

When the static keyword is removed the output is:

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:

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:

When the static keyword is removed the output is:

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:

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:
They cannot access non-static class member data using the member-selection operators (. or –>).
They cannot be declared as virtual.
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.