Am avut 371713 vizite de la lansarea siteului.




Inapoi        Inainte       Cuprins

Type conversion example

An example in which automatic type conversion is extremely helpful occurs with any class that encapsulates character strings (in this case, we will just implement the class using the Standard C++ string class because it’s simple). Without automatic type conversion, if you want to use all the existing string functions from the Standard C library, you have to create a member function for each one, like this:

//: C12:Strings1.cpp
// No auto type conversion
#include "../require.h"
#include <cstring>
#include <cstdlib>
#include <string>
using namespace std;

class Stringc {
  string s;
public:
  Stringc(const string& str = "") : s(str) {}
  int strcmp(const Stringc& S) const {
    return ::strcmp(s.c_str(), S.s.c_str());
  }
  // ... etc., for every function in string.h
};

int main() {
  Stringc s1("hello"), s2("there");
  s1.strcmp(s2);
} ///:~

Here, only the strcmp( ) function is created, but you’d have to create a corresponding function for every one in <cstring> that might be needed. Fortunately, you can provide an automatic type conversion allowing access to all the functions in <cstring>:

//: C12:Strings2.cpp
// With auto type conversion
#include "../require.h"
#include <cstring>
#include <cstdlib>
#include <string>
using namespace std;

class Stringc {
  string s;
public:
  Stringc(const string& str = "") : s(str) {}
  operator const char*() const { 
    return s.c_str(); 
  }
};

int main() {
  Stringc s1("hello"), s2("there");
  strcmp(s1, s2); // Standard C function
  strspn(s1, s2); // Any string function!
} ///:~

Now any function that takes a char* argument can also take a Stringc argument because the compiler knows how to make a char* from a Stringc.

Pitfalls in automatic type conversion

Because the compiler must choose how to quietly perform a type conversion, it can get into trouble if you don’t design your conversions correctly. A simple and obvious situation occurs with a class X that can convert itself to an object of class Y with an operator Y( ). If class Y has a constructor that takes a single argument of type X, this represents the identical type conversion. The compiler now has two ways to go from X to Y, so it will generate an ambiguity error when that conversion occurs:

//: C12:TypeConversionAmbiguity.cpp
class Orange; // Class declaration

class Apple {
public:
  operator Orange() const; // Convert Apple to Orange
};

class Orange {
public:
  Orange(Apple); // Convert Apple to Orange
};

void f(Orange) {}

int main() {
  Apple a;
//! f(a); // Error: ambiguous conversion
} ///:~

The obvious solution to this problem is not to do it. Just provide a single path for automatic conversion from one type to another.

A more difficult problem to spot occurs when you provide automatic conversion to more than one type. This is sometimes called fan-out:

//: C12:TypeConversionFanout.cpp
class Orange {};
class Pear {};

class Apple {
public:
  operator Orange() const;
  operator Pear() const;
};

// Overloaded eat():
void eat(Orange);
void eat(Pear);

int main() {
  Apple c;
//! eat(c);
  // Error: Apple -> Orange or Apple -> Pear ???
} ///:~

Class Apple has automatic conversions to both Orange and Pear. The insidious thing about this is that there’s no problem until someone innocently comes along and creates two overloaded versions of eat( ). (With only one version, the code in main( ) works fine.)

Again, the solution – and the general watchword with automatic type conversion – is to provide only a single automatic conversion from one type to another. You can have conversions to other types; they just shouldn’t be automatic. You can create explicit function calls with names like makeA( ) and makeB( ).

Hidden activities

Automatic type conversion can introduce more underlying activities than you may expect. As a little brain teaser, look at this modification of CopyingVsInitialization.cpp:

//: C12:CopyingVsInitialization2.cpp
class Fi {};

class Fee {
public:
  Fee(int) {}
  Fee(const Fi&) {}
};

class Fo {
  int i;
public:
  Fo(int x = 0) : i(x) {}
  operator Fee() const { return Fee(i); }
};

int main() {
  Fo fo;
  Fee fee = fo;
} ///:~

There is no constructor to create the Fee fee from a Fo object. However, Fo has an automatic type conversion to a Fee. There’s no copy-constructor to create a Fee from a Fee, but this is one of the special functions the compiler can create for you. (The default constructor, copy-constructor, operator=, and destructor can be synthesized automatically by the compiler.) So for the relatively innocuous statement

Fee fee = fo;

the automatic type conversion operator is called, and a copy-constructor is created.

Use automatic type conversion carefully. As with all operator overloading, it’s excellent when it significantly reduces a coding task, but it’s usually not worth using gratuitously.

Summary

The whole reason for the existence of operator overloading is for those situations when it makes life easier. There’s nothing particularly magical about it; the overloaded operators are just functions with funny names, and the function calls happen to be made for you by the compiler when it spots the right pattern. But if operator overloading doesn’t provide a significant benefit to you (the creator of the class) or the user of the class, don’t confuse the issue by adding it.

Exercises

Solutions to selected exercises can be found in the electronic document The Thinking in C++ Annotated Solution Guide, available for a small fee from www.BruceEckel.com.

  1. Create a simple class with an overloaded operator++. Try calling this operator in both pre- and postfix form and see what kind of compiler warning you get.
  2. Create a simple class containing an int and overload the operator+ as a member function. Also provide a print( ) member function that takes an ostream& as an argument and prints to that ostream&. Test your class to show that it works correctly.
  3. Add a binary operator- to Exercise 2 as a member function. Demonstrate that you can use your objects in complex expressions like
    a + b – c.
  4. Add an operator++ and operator-- to Exercise 2, both the prefix and the postfix versions, such that they return the incremented or decremented object. Make sure that the postfix versions return the correct value.
  5. Modify the increment and decrement operators in Exercise 4 so that the prefix versions are non-const and the postfix versions are const. Show that they work correctly and explain why this would be done in practice.
  6. Change the print( ) function in Exercise 2 so that it is the overloaded operator<< as in IostreamOperatorOverloading.cpp.
  7. Modify Exercise 3 so that the operator+ and operator- are non-member functions. Demonstrate that they still work correctly.
  8. Add the unary operator- to Exercise 2 and demonstrate that it works correctly.
  9. Create a class that contains a single private char. Overload the iostream operators << and >> (as in IostreamOperatorOverloading.cpp) and test them. You can test them with fstreams, stringstreams, and cin and cout.
  10. Determine the dummy constant value that your compiler passes for postfix operator++ and operator--.
  11. Write a Number class that holds a double, and add overloaded operators for +, –, *, /, and assignment. Choose the return values for these functions so that expressions can be chained together, and for efficiency. Write an automatic type conversion operator int( ).
  12. Modify Exercise 11 so that the return value optimization is used, if you have not already done so.
  13. Create a class that contains a pointer, and demonstrate that if you allow the compiler to synthesize the operator= the result of using that operator will be pointers that are aliased to the same storage. Now fix the problem by defining your own operator= and demonstrate that it corrects the aliasing. Make sure you check for self-assignment and handle that case properly.
  14. Write a class called Bird that contains a string member and a static int. In the default constructor, use the int to automatically generate an identifier that you build in the string, along with the name of the class (Bird #1, Bird #2, etc.). Add an operator<< for ostreams to print out the Bird objects. Write an assignment operator= and a copy-constructor. In main( ), verify that everything works correctly.
  15. Write a class called BirdHouse that contains an object, a pointer and a reference for class Bird from Exercise 14. The constructor should take the three Birds as arguments. Add an operator<< for ostreams for BirdHouse. Write an assignment operator= and a copy-constructor. In main( ), verify that everything works correctly. Make sure that you can chain assignments for BirdHouse objects and build expressions involving multiple operators.
  16. Add an int data member to both Bird and BirdHouse in Exercise 15. Add member operators +, -, *, and / that use the int members to perform the operations on the respective members. Verify that these work.
  17. Repeat Exercise 16 using non-member operators.
  18. Add an operator-- to SmartPointer.cpp and NestedSmartPointer.cpp.
  19. Modify CopyingVsInitialization.cpp so that all of the constructors print a message that tells you what’s going on. Now verify that the two forms of calls to the copy-constructor (the assignment form and the parenthesized form) are equivalent.
  20. Attempt to create a non-member operator= for a class and see what kind of compiler message you get.
  21. Create a class with a copy-constructor that has a second argument, a string that has a default value that says “CC call.” Create a function that takes an object of your class by value and show that your copy-constructor is called correctly.
  22. In CopyingWithPointers.cpp, remove the operator= in DogHouse and show that the compiler-synthesized operator= correctly copies the string but simply aliases the Dog pointer.
  23. In ReferenceCounting.cpp, add a static int and an ordinary int as data members to both Dog and DogHouse. In all constructors for both classes, increment the static int and assign the result to the ordinary int to keep track of the number of objects that have been created. Make the necessary modifications so that all the printing statements will say the int identifiers of the objects involved.
  24. Create a class containing a string as a data member. Initialize the string in the constructor, but do not create a copy-constructor or operator=. Make a second class that has a member object of your first class; do not create a copy-constructor or operator= for this class either. Demonstrate that the copy-constructor and operator= are properly synthesized by the compiler.
  25. Combine the classes in OverloadingUnaryOperators.cpp and Integer.cpp.
  26. Modify PointerToMemberOperator.cpp by adding two new member functions to Dog that take no arguments and return void. Create and test an overloaded operator->* that works with your two new functions.
  27. Add an operator->* to NestedSmartPointer.cpp.
  28. Create two classes, Apple and Orange. In Apple, create a constructor that takes an Orange as an argument. Create a function that takes an Apple and call that function with an Orange to show that it works. Now make the Apple constructor explicit to demonstrate that the automatic type conversion is thus prevented. Modify the call to your function so that the conversion is made explicitly and thus succeeds.
  29. Add a global operator* to ReflexivityInOverloading.cpp and demonstrate that it is reflexive.
  30. Create two classes and create an operator+ and the conversion functions such that addition is reflexive for the two classes.
  31. Fix TypeConversionFanout.cpp by creating an explicit function to call to perform the type conversion, instead of one of the automatic conversion operators.
  32. Write simple code that uses the +, -, *, and / operators for doubles. Figure out how your compiler generates assembly code and look at the assembly language that’s generated to discover and explain what’s going on under the hood.


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)