Inapoi
Inainte
Cuprins
Automatic operator=
creation
Because assigning an object to another
object of the same type is an activity most people expect to be possible,
the compiler will automatically create a type::operator=(type) if you
don’t make one. The behavior of this operator mimics that of the
automatically created copy-constructor; if the class contains objects (or is
inherited from another class), the operator= for those objects is called
recursively. This is called memberwise
assignment. For
example,
//: C12:AutomaticOperatorEquals.cpp
#include <iostream>
using namespace std;
class Cargo {
public:
Cargo& operator=(const Cargo&) {
cout << "inside Cargo::operator=()" << endl;
return *this;
}
};
class Truck {
Cargo b;
};
int main() {
Truck a, b;
a = b; // Prints: "inside Cargo::operator=()"
} ///:~
The automatically generated
operator= for Truck calls
Cargo::operator=.
In general, you don’t want to let
the compiler do this for you. With classes of any sophistication (especially if
they contain pointers!) you want to explicitly create an operator=. If
you really don’t want people to perform assignment, declare
operator= as a
private
function. (You don’t need to define it unless you’re using it inside
the
class.)
Automatic type conversion
In C and C++, if the compiler sees an
expression or function call using a type that isn’t quite the one it
needs, it can often perform an automatic type conversion from the type it has to
the type it
wants.
In C++, you can achieve this same effect for user-defined types by defining
automatic type conversion functions. These functions come in two flavors: a
particular type of constructor and an overloaded
operator.
Constructor conversion
If you define a
constructor that takes as its single argument an object
(or reference) of another type, that constructor allows the compiler to perform
an automatic type conversion. For example,
//: C12:AutomaticTypeConversion.cpp
// Type conversion constructor
class One {
public:
One() {}
};
class Two {
public:
Two(const One&) {}
};
void f(Two) {}
int main() {
One one;
f(one); // Wants a Two, has a One
} ///:~
When the compiler sees f( )
called with a One object, it looks at the declaration for
f( ) and notices it wants a Two. Then it looks to see if
there’s any way to get a Two from a One, and it finds the
constructor Two::Two(One), which it quietly calls. The resulting
Two object is handed to f( ).
In this case, automatic type conversion
has saved you from the trouble of defining two overloaded versions of
f( ). However, the cost is the hidden constructor call to
Two, which may matter if you’re concerned about the efficiency of
calls to f( ).
Preventing constructor
conversion
There are times when automatic type
conversion via the constructor can cause problems. To turn it off, you modify
the constructor by prefacing with the keyword
explicit (which only works with constructors).
Used to modify the constructor of class Two in the example
above:
//: C12:ExplicitKeyword.cpp
// Using the "explicit" keyword
class One {
public:
One() {}
};
class Two {
public:
explicit Two(const One&) {}
};
void f(Two) {}
int main() {
One one;
//! f(one); // No auto conversion allowed
f(Two(one)); // OK -- user performs conversion
} ///:~
By making Two’s constructor
explicit, the compiler is told not to perform any automatic conversion using
that particular constructor (other non-explicit constructors in that
class can still perform automatic conversions). If the user wants to make the
conversion happen, the code must be written out. In the code above,
f(Two(one)) creates a
temporary object of type
Two from one, just like the compiler did in the previous
version.
Operator conversion
The second way to produce automatic type
conversion is through operator
overloading.
You can create a member function that takes the current type and converts it to
the desired type using the operator keyword followed by the type you want
to convert to. This form of operator overloading is unique because you
don’t appear to specify a return type – the return type is the
name of the operator you’re overloading. Here’s an
example:
//: C12:OperatorOverloadingConversion.cpp
class Three {
int i;
public:
Three(int ii = 0, int = 0) : i(ii) {}
};
class Four {
int x;
public:
Four(int xx) : x(xx) {}
operator Three() const { return Three(x); }
};
void g(Three) {}
int main() {
Four four(1);
g(four);
g(1); // Calls Three(1,0)
} ///:~
With the constructor technique, the
destination class is performing the conversion, but with operators, the source
class performs the conversion. The value of the constructor technique is that
you can add a new conversion path to an existing system as you’re creating
a new class. However, creating a single-argument constructor always
defines an automatic type conversion (even if it’s got more than one
argument, if the rest of the arguments are defaulted), which may not be what you
want (in which case you can turn it off using explicit). In addition,
there’s no way to use a constructor conversion from a user-defined type to
a built-in type; this is possible only with operator
overloading.
Reflexivity
One of the most convenient reasons to use
global overloaded operators instead of member operators
is that in the global versions, automatic type
conversion may be applied to either operand, whereas with member objects, the
left-hand operand must already be the proper type. If you want both operands to
be converted, the global versions can save a lot of coding. Here’s a small
example:
//: C12:ReflexivityInOverloading.cpp
class Number {
int i;
public:
Number(int ii = 0) : i(ii) {}
const Number
operator+(const Number& n) const {
return Number(i + n.i);
}
friend const Number
operator-(const Number&, const Number&);
};
const Number
operator-(const Number& n1,
const Number& n2) {
return Number(n1.i - n2.i);
}
int main() {
Number a(47), b(11);
a + b; // OK
a + 1; // 2nd arg converted to Number
//! 1 + a; // Wrong! 1st arg not of type Number
a - b; // OK
a - 1; // 2nd arg converted to Number
1 - a; // 1st arg converted to Number
} ///:~
Class Number has both a member
operator+ and a friend operator–. Because
there’s a constructor that takes a single int argument, an
int can be automatically converted to a Number, but only under the
right conditions. In main( ), you can see that adding a
Number to another Number works fine because it’s an exact
match to the overloaded operator. Also, when the compiler sees a Number
followed by a + and an int, it can match to the member function
Number::operator+ and convert the int argument to a Number
using the constructor. But when it sees an int, a +, and a
Number, it doesn’t know what to do because all it has is
Number::operator+, which requires that the left operand already be a
Number object. Thus, the compiler issues an error.
With the friend
operator–, things are different. The compiler needs to fill in both
its arguments however it can; it isn’t restricted to having a
Number as the left-hand argument. Thus, if it sees
1 – a
it can convert the first argument to a
Number using the constructor.
Sometimes you want to be able to restrict
the use of your operators by making them members. For example, when multiplying
a matrix by a vector, the vector must go on the right. But if you want your
operators to be able to convert either argument, make the operator a friend
function.
Fortunately, the compiler will not take
1 – 1 and convert both arguments to Number objects and then
call operator–. That would mean that existing C code might suddenly
start to work differently. The compiler matches the “simplest”
possibility first, which is the built-in operator for the expression 1
–
1.
 |
|