1

I understand that developers are able to overload operator[] to either get or set something depending on which side the operator is on. For example you can do obj[2] = test_obj; or test_obj = obj[2]. However I don't understand how the first example works; operator[] returns a reference to something on the heap, so when obj[2] = test_obj; is run, what is happening? If there was something at index 2 already, does it get deleted? does the destructor of the object at 2 get called first? How is this all done since this type of checking isn't done in the operator[]'s method body?

b4hand
  • 9,550
  • 4
  • 44
  • 49
JustKash
  • 687
  • 2
  • 13
  • 28
  • 3
    This is all handled by the reference mechanics. Look up those, and you'll understand this. – user2357112 Oct 28 '14 at 00:35
  • `operator[]` returns a reference yes, but if it's on the heap that's mere coincidence. It can also be on the stack, or any other arbitrary place in memory. Deletion is usually a non-issue – Mooing Duck Oct 28 '14 at 00:39

2 Answers2

3

References work very similar to pointers, but references always point to some actual value.

enter image description here

So when operator[] returns a reference, and you assign to that reference, you're assigning directly to the value to which the reference refers. In your example, this would be the object located at index 2. Note that the returned type of Container::operator[] is not a reference to the Container, but rather it is typically a reference to the contained values.

To give a detailed answer it depends on the actual return type of operator[] for the type in question. If the return type is a reference to a primitive type, then it works just like any other reference to a primitive. There are no destructors involved. The assignment takes place directly in the referred value. For example:

int x = 5;
x = 6;

No destructors are called. No operators are called. The value contained by x is replaced by the value on the right hand side of the assignment operator.

Similarly if a reference is involved:

int x = 5;
int &r = x;
r = 6;

No destructors are called. No operators are called. The value to which r points (which in this case is x) is replaced by the value on the right hand side of the assignment operator.

Similarly, if a non-primitive type is involved, then the operator= for the given type will be called.

Foo x;
Foo y;
// Foo::operator=(const Foo &) is called on x with an argument referencing y
x = y;

Neither x nor y's destructor will necessarily be called.

Same thing with a reference:

Foo x, y;
Foo &r = x;
// Foo::operator=(const Foo &) is called on x with an argument referencing y
r = y;

However, operator[] is not required to return a reference; although, that is strongly encouraged.

For non-references or for references to non-primitive types, the operator= of the returned value is invoked, and thus the behavior of the assignment operator is also important.

In the case of operator= the destructor for the left hand side is not invoked, but the assignment operator is itself responsible for any cleanup. This is why assignment operators are often implemented in terms of the copy-swap idiom.

So in the case of std::vector<int>, std::vector<int>::operator[] returns an int &. The assignment happens in place in the location wherever the reference points.

For a std::vector<Foo>, std::vector<Foo>::operator[] returns a Foo & and Foo::operator=(const Foo &) is invoked.

However like I mentioned, a class could return an arbitrary object from operator[] and that object's assignment operator will be called. This can be used to proxy the assignment through another object. A place where you might see this is in a Matrix class that returns a Row object which might in turn implement operator[]. Again this design is not necessarily recommended, but it is not unheard of. In these cases, the return type may not be a reference at all, but an actual value. However, it still works the same way. The Row::operator= or Proxy::operator= is responsible for allocating and clearing any resources necessary. This is why the Rule of Three is so important.

Community
  • 1
  • 1
b4hand
  • 9,550
  • 4
  • 44
  • 49
  • This is the answer that I was looking for but I already accepted a previous answer that was further clarified in the comments section of the answer. – JustKash Oct 28 '14 at 19:12
1

Since operator[] typically returns a reference, it works precisely the same as if a reference was on the left side:

Foo& j;
j = test_obj;

A reference basically works the same as if a regular variable were on the left, since a reference is basically just another way to refer to a variable. So it's essentially the same as:

Foo j;
j = test_obj;

So it's basically just copy assignment, using Foo::operator=(Foo const&) (which always exists in C++). (Even if it's deleted, it still exists -- you will get an error if you try to call it saying that it's deleted, not that no function exists.)

David Schwartz
  • 179,497
  • 17
  • 214
  • 278
  • Please explain the downvote so the answer can be corrected or improved. – David Schwartz Oct 28 '14 at 00:42
  • But this doesn't answer how it knows to delete what is there already. For example, if it returned a reference to an object that was dynamically allocated on the heap, does the compiler automatically delete it first and then dynamically allocate new space on the heap for the object on the rhs and call its constructor? None of this is in the operator[]'s body so is it happening behind the scenes? – JustKash Oct 28 '14 at 00:47
  • No, none of that stuff automatically happens. If there is something that needs to be freed, the operator[] does not free it for you. – StilesCrisis Oct 28 '14 at 00:54
  • @Akash: you seem to be misunderstanding how assignment works. It doesn't destroy and recreate a new object; it reassigns the value of the existing object, using its assignment operator. That's independent of the `[]` operator, which simply gives a reference to the object that's being assigned to. – Mike Seymour Oct 28 '14 at 00:56
  • @MikeSeymour: The assignment operator gives the developer access to the rhs src object and the current object within the method body to do w/e, now this usually includes deleting the dynamically allocated data of the current object and dynamically allocating the new data from the src to score in the current obj. However the operator[] seems to do some other things in the background where the developer doesn't have to explicitly delete the dynamically allocated data in the current object before assigning the new data from the rhs src. – JustKash Oct 28 '14 at 01:05
  • @Akash Read my answer. The `operator[]` function just returns a reference. What happens to that reference (whether it's assigned to or whatever) is not `operator[]`'s business. – David Schwartz Oct 28 '14 at 01:06
  • @Akash: No, it's not doing anything like that. `[]` gives a reference to an object; `=` calls `operator=` on that object. Neither deletes the object; assignment changes the value of an existing object, and doesn't create or destroy it. – Mike Seymour Oct 28 '14 at 01:07
  • I have not downvoted or upvoted anything so far, but what I don't understand is since if you are using the operator[], it suggests that the object is probably a collection of some sort, so is the assignment opeartor being called on the collection object or the object that is returned as a reference from the operator[]? It would have to be the returned reference right? So operator[] returns a reference to an object and the assignment operator of that object is called with the rhs object as the source. I think that makes sense; can someone confirm if this is right. – JustKash Oct 28 '14 at 01:18
  • 1
    @Akash yes the returned object's assignment operator is called not the container. – b4hand Oct 28 '14 at 01:21
  • 1
    @Akash Yes, `operator[]` returns a reference. If that reference is on the left hand side of an assignment operator, then the variable the reference references has its value changed. – David Schwartz Oct 28 '14 at 06:16