Am avut 371707 vizite de la lansarea siteului.




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.

Home   |   Web Faq   |   Radio Online   |   About   |   Products   |   Webmaster Login

The quality software developer.™
© 2003-2004 ruben|labs corp. All Rights Reserved.
Timp de generare a paginii: 17583 secunde
Versiune site: 1.8 SP3 (build 2305-rtm.88542-10.2004)