overloading [] problem

in my book, I saw a class named ArrayTp has this public function:
1
2
virtual T & operator[](int i);
virtual T operator[](int i )const;

inside the each definition, the code are same.
what's the difference between the 2 forms? what's the usage of virtual and const, why can't & with the const?

and I saw another template class:
1
2
3
4
5
6
7
8
9
template <typename T1>
class Pair {
private:
    T1 a;
public:
   T1 & first() {return a;}
    T1 first()const {return a;}
..
}

also I'm confused the 2 froms, with const not &, what does it mean? why have the 2 form represent 1 thing?
Last edited on
A const member method does not allow modifying the instance. So here's the difference:
1
2
virtual T& operator[](int i); // used for assigning the value at i. Usage: a[0] = 22;
virtual T operator[](int i) const; // used for reading the value at i. Usage: int v = a[0]; 


The virtual keyword allows for derived classes to override these methods.

You cannot return a non-const reference type if the the method is marked as const because:
A const member method does not allow modifying the instance.


If a method returns a non-const reference type (i.e. int&), you are not returning the value, but rather a pointer to the member variable.

Here is a good picture about the differences:
http://www.parashift.com/c++-faq/const-member-fns.html

For more information, read up on C++ "const correctness".
if there's no derived class, why the example use the virtual? Does it have some advantage?
I still can't understand this:
1
2
   int & first() {return a;}
    int first()const {return a;}

what's the difference in the usage?
Last edited on
if there's no derived class, why the example use the virtual? Does it have some advantage?


The only reason to do it is to allow for the possibility of a derived class to reimplement that function/operator. So if you wanted to make a derived class... you could do it without having to modify the base class.

I still can't understand this:


'const correctness' is an optimization thing. Usage of 'const' objects tells the compiler "this object doesn't change", so it can take certain shortcuts in the code.

However, to make that "it doesn't change" promise, it imposes restrictions on the class.

1) class members cannot be modified. After all, if you're modifying them, you're breaking the promise:

1
2
const SomeClass object;
object.foo = 5;  // illegal since 'object' is const.  The compiler will error. 


2) You can only call 'const' member functions with a const object. 'const' member functions impose the same restriction... they cannot modify the class members. Non-const functions have no restriction, therefore they cannot be called.

1
2
3
4
5
const SomeClass obj;

obj.AConstFunction();  // OK
obj.ANonConstFunction();  // compiler error, because the non-const function might modify
  // the object, breaking the 'const' promise. 


3) As an extension of #2, const functions cannot modify the object. Meaning they must also follow rules 1 and 2: They cannot modify member variables, nor can they call other non-const functions.


Your example:
1
2
    T1 & first() {return a;}
    T1 first()const {return a;}


Your example code is providing a const "read only" version of the 'first' function, which allows 'a' to be read when you have a const object.

It also overloads the 'first' function with a non-const version which allows 'a' to be written, but only if the object is non-const. It allows writing by returning a reference to the 'a' variable (hence the & in the return type), rather than returning a copy of the 'a' variable (no & symbol).

Example usage:

1
2
3
4
5
6
7
8
9
10
11
SomeClass nc;
const SomeClass c;

// both objects can be used to read a:
x = nc.first();  // OK - calls non-const version, which assigns "x = a"
x = c.first();   // OK - calls const version which assigns "x = copy_of_a"

// but only the nc version can be used to write
nc.first() = 5;  // OK - calls non-const version, which assigns "a = 5"
c.first() = 5; // Compiler error, the non-const version does not allow writing because
   // it returns a value, not a reference. 
The importance of virtual method can be exhibited by polymorphism. That is on a separate topic, and if you have questions about that, feel free to ask. Technically, a method can be overriden regardless whether it is marked as virtual or not. But without the virtual indicator, the method invocation will not go through the virtual table to figure out which right method to call (because without the virtual keyword, an early binding is being done). You are seeing the methods of ArrayTP marked as virtual probably because the author intends someone to derive from this class. If there are no classes deriving from ArrayTP, then these methods work as it is.

1
2
3
4
// This method returns a reference to the "a" member. This is providing access to directly modify a.
T1& first() {return a;}
// This method returns a const copy of the "a" member. This is providing a copy of a most likely to get its current state.
T1 first() const {return a;}


To demonstrate a usage on that class:
1
2
3
4
Pair<int> intPair; // assume that "a" member got initialized.
int aValue = intPair.first(); // this calls the T1 first() const version
int newValue = 0;
intPair.first() = newValue; // this calls the T1& first() version. After this line, the value of "a" member is set to 0. 
suppose T1=class Employee is a class that has the following two methods:

1
2
3
4
5
6
7
8
9
10
11
12
13
void Employee::OutputDetails() const
{
   ...
   // Does not change Employee instance but merely displays
}


void Employee::AccumulateTax(double taxAmt)
{
   ...
   // Does make change to Employee instance
}


Now if your Employee instance was stored in a Pair class then when call:

1
2
3
4
5
Pair<Employee> empPair;

empPair.first.OutputDetails();   // will call the const version of first

empPair.first.AccumulateTax(2500.95);   // will call the non-const version of first 


In actual fact the 2nd call will return us an actual reference to the Employee instance - which means that any changes done will not be done to a copy of the Empoyee instance, but to the actual one.

thank you all guys help me.
I'm wonderign is this work ?
T1 & first()const {return a;}

with so many forms, it confused me, when should I use & or const?
Last edited on
I'm wonderign is this work ?


No that will not work.

since the function is const, that means the class (and all of its members) are also const, which means 'a' is a const T1. You cannot return a non-const reference to a const variable.


when should I use & or const?


You should use const when the function does not modify the class's state. You should use & when you want a reference.

If you are unsure what references are, there is a section on them in the tutorials section of this site. But basically a reference is an alias to an existing variable (it is not a variable itself -- it's just another name by which one can be accessed).

Example:

1
2
3
4
5
6
7
int var = 3;  // var is a variable
int& ref = var;  // ref is a reference, referring to 'var'
 // this means 'var' and 'ref' are different names for the same variable
 //  both names can be used interchangably:

ref = 10;  // changes 'var' to 10
cout << var;  // prints 10 



References can also be used as function parameters:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void example(int val, int& ref)  // a param by value and a param by reference
{
  val = 0;  // val is its own variable.  It's a copy of the param passed to it. So changing
    // val in this function is not visible from main()

  ref = 1;  // ref is not its own variable, but instead is referring to whatever variable was
   // passed to the function. So here we are modifying the passed parameter.  This will
   //  modify the variable in main
}

int main()
{
  int a = 9;
  int b = 9;

  example( a, b );  // 'a' passed by value, 'b' passed by reference
  
  cout << a;  // prints 9, 'a' has not been changed
  cout << b;  // prints 1, 'b' has been changed by the 'example' function.
}



And, as in your example, references can also be returned from functions. But this is trickier because they have to refer to variables with a broader scope. Really, the only time you'll see this is in container classes which allow you to access one of their elements.

An impractical example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int global_A = 0;
int global_B = 0;

int& example( bool want_a ) // returns a reference
{
  if( want_a )
    return global_A;  // the reference will be either to global_A or global_B
  return global_B;  // depending on the want_a parameter
}

int main()
{
  example( true ) = 5;  // example(true) returns a reference to global_A, which
    // we are assigning 5 to.  So this is like assigning 5 to global_A

  example( false ) = 10;  // likewise, this is like assigning 10 to global_B

  cout << global_A;  // prints 5
  cout << example(true);  // also prints 5

  cout << global_B;  // prints 10
  cout << example(false); // also prints 10
}
Last edited on
Topic archived. No new replies allowed.