How to distinguish read / write operations using the [] operator

I need to write a class with an overloaded operator [], which has a different behavior when the operator [] is used to read or write data. To give a practical example of what I want to achieve, let's say I need to write an implementation of the PhoneBook class, which can be used as follows:

PhoneBook phoneBook(999999); // 999999 is the default number which should be // used when calling someone who is not in the phone book phoneBook["Paul"] = 234657; // adds Paul number phoneBook["John"] = 340156; // adds John number // next line should print Paul number 234657 cout << "To call Paul dial " << phoneBook["Paul"] << endl; // next line should print John number 340156 cout << "To call John dial " << phoneBook["John"] << endl; // next line should print 999999 because Frank is not in the phone book cout << "To call Frank dial " << phoneBook["Frank"] << endl; 

The problem is that when using

 phoneBook["Frank"] 

I don’t want to add an entry to the phone book for Frank, otherwise a solution based on std :: map would be easy to implement.

I have not found on the Internet any standard way to achieve this. after some thought, I came up with the following solution, in which the [] operator returns a “temporary object” called PhoneNumber. PhoneNumber is then used to distinguish between read / write operations:

 #include <iostream> #include <string> #include <map> using namespace std; class PhoneBook{ private: map<string, int> data_; // stores phone numbers int defaultNumber_; // default number returned when no matching name is found public: PhoneBook(int defaultNumber) : defaultNumber_(defaultNumber) {} // Searches in the phone book for a name. If the name is found it returns // the corresponding number. If the name is not found it returns defaultNumber_ int read(string name){ map<string, int>::iterator it = data_.find(name); if (it==data_.end()){ return defaultNumber_; } else { return it->second; } } // Forwarding function to map operator []. It is not really necessary but it is added for clarity int& write(string name){ return data_[name]; } // Forward declaration of the "temporary object" returned by operator [] // See declaration below class PhoneNumber; PhoneNumber operator[](string name){ return PhoneNumber(this, name); } class PhoneNumber{ friend class PhoneBook; private: PhoneBook* const phoneBook_; string name_; // Constructors are private so that PhoneNumber can be used only by PhoneBook // Default constructor should not be used PhoneNumber() : phoneBook_(NULL) {} PhoneNumber(PhoneBook* phoneBook, string name) : phoneBook_(phoneBook), name_(name) {} public: // conversion to int for read operations operator int (){ return phoneBook_->read(name_); } // assignment operator for write operations const int& operator = (const int& val){ return phoneBook_->write(name_) = val; } }; }; int main(){ PhoneBook phoneBook(999999); phoneBook["Paul"] = 234657; phoneBook["John"] = 340156; cout << "To call Paul dial " << phoneBook["Paul"] << endl; cout << "To call John dial " << phoneBook["John"] << endl; cout << "To call Frank dial " << phoneBook["Frank"] << endl; return 0; } 

The PhoneBook class behaves as we would like, and the program prints:

 To call Paul dial 234657 To call John dial 340156 To call Frank dial 999999 

I would like to ask you some questions:

  • Is there a better way to get a class behaving like class I encoded?
  • Do I have a method with which I can find additional information about this?
  • Do you see a flaw / possible improvement in my solution?

In the library that I am writing, allowing the behavior that I received for the PhoneBook :: [] operator in such a situation is really important, and I would really like to know what you think about my problem.

Thanks!

+8
c ++
source share
2 answers

What you offer is the standard solution to this problem. Commonly known as a proxy template or proxy idiom, and the helper class that you return is called a proxy. (Since this is a nested class, just calling it Proxy is usually enough.)

+8
source share

I think you can implement two versions of the [] operator, one with a constant modifier, and the other without. Then, if you have an object say PhoneBook phoneBook(999999); if phoneBook is a const object, only operator [] const can be called. If phoneBook is not a constant object, operator [] is called by default. If you want to call operator [] const , if a non-constant object is specified, you can add a listing, for example static_cast<const PhoneBook&>(phoneBook)->operator[...] .

 #include <iostream> #include <string> #include <map> using namespace std; class PhoneBook{ private: map<string, int> data_; // stores phone numbers int defaultNumber_; // default number returned when no matching name is found public: PhoneBook(int defaultNumber) : defaultNumber_(defaultNumber) {} int operator [] (const string& name) const { map<string, int>::const_iterator it = data_.find(name); if (it == data_.end()) { return defaultNumber_; } else { return it->second; } } int& operator [] (const string& name) { return data_[name]; } }; int main(){ PhoneBook phoneBook(999999); phoneBook["Paul"] = 234657; phoneBook["John"] = 340156; cout << "To call Paul dial " << phoneBook["Paul"] << endl; cout << "To call John dial " << phoneBook["John"] << endl; cout << "To call Frank dial " << static_cast<const PhoneBook&>(phoneBook)["Frank"] << endl; return 0; } 
-one
source share

All Articles