Serge C
Serge C

Reputation: 2283

Reference to Abstract Class assign problem

In relation to question "reference to abstract class" I wrote the following example:

#include <iostream>
#include <vector>

class data
{
  public:
    virtual int get_value() = 0;
};

class data_impl : public data
{
  public:
    data_impl( int value_ ) : value( value_ ) {}
    virtual int get_value() { return value; }
  private:
    int value;
};

class database
{
  public:
    data& get( int index ) { return *v[index]; }
    void  add( data* d ) { v.push_back( d ); }
  private:
    std::vector< data* > v;
};

int main()
{
    data_impl d1( 3 );
    data_impl d2( 7 );

    database db;
    db.add( &d1 );
    db.add( &d2 );

    data& d = db.get( 0 );
    std::cout << d.get_value() << std::endl;
    d = db.get( 1 );
    std::cout << d.get_value() << std::endl;

    data& d_ = db.get( 1 );
    std::cout << d_.get_value() << std::endl;
    d_ = db.get( 0 );
    std::cout << d_.get_value() << std::endl;

    return 0;
}

To my surprise, the example prints:

3
3
7
7

and it looks like the reference assignment does the work differently from my expectation. I would expect:

3
7
7
3

Could you please point what my mistake is?

Thank you!

Upvotes: 2

Views: 1991

Answers (6)

Matthieu M.
Matthieu M.

Reputation: 299890

The problem can be seen here:

data& d = /**/;

d = /**/;        <---

Here, the static type of d is data&, meaning that it is in fact syntactic sugar for:

d.operator=(/**/);

Since data::operator= is not virtual, then it is called. No dispatch to a derived class. And since data is a pure interface, it does not have any attribute, so the code is meaningless.


The problem is, there are no real solution here:

  • Making operator= virtual is a mess, because the parameter must be a data const& in data, and cannot later be changed in subsequent derived classes, meaning a loss of type safety
  • Using a setValue/getValue virtual pair will only work if the derived classes do not hold more data than they present via this interface, which is an unreasonnable assumption

Since the problem cannot be solved, the only way out is to make diagnosis easier: disabling copy and assignment in the base class.

There are two ways to do this:

  • Make the whole hierarch non-copyable (either inherit from boost::noncopyable or delete the copy operator and copy assignment operator, etc...)

  • Make the current class non-copyable, but allow derived classes to be copyable automatically, by making the copy constructor and assignment operator protected.

In general, it is an error for a non-final class to have a public copy constructor and assignment operator (I wonder if any compiler diagnose this).

Upvotes: 1

Zhen
Zhen

Reputation: 4283

You can't separate the reference from the referent. enter link description here

Unlike a pointer, once a reference is bound to an object, it can not be "reseated" to another object. The reference itself isn't an object (it has no identity; taking the address of a reference gives you the address of the referent; remember: the reference is its referent).

The strangest thing is the compiler don't warn it.

I try with gcc -Wall -pedantic

Upvotes: 0

I am going to simplify the example a bit:

data_impl a(3), b(7);
data &ra(a);
data &rb(b);
std::cout << ra.get_value() << std::endl;
ra = rb;                                  // [1]
std::cout << ra.get_value() << std::endl;

Now with that simplified code it is easier to reason about the program. You obtain a reference ra to the a subobject, but the reference is of type data, rather than data_impl. Similarly with rb and b. In the line marked with [1] you are performing an assignment from rb to ra, the static type of the two arguments of the expression is data, and that means that regardless of what the real object they refer to is, that particular line is assigning only the data subobject. This is called slicing.

That is, [1] is setting the data subobject of a to be the same as the data subobject of b. Because data does not contain any actual data, the result is that a remains unmodified.

For a more illustrative example, you can add a field to data, and check that the expression does modify that field in a, even if it does not modified the fields in derived classes.

Then you can try an implement operator= manually as a virtual function and check that you can get the expected result, but the implementation will be a bit messier, as there are no covariant arguments to member functions in C++, which means that the signature of operator= at all levels will take a (const) reference to data, and you will be forced to downcast (verify that the cast succeeded) and then perform the assignment...

Upvotes: 2

P&#233;ter T&#246;r&#246;k
P&#233;ter T&#246;r&#246;k

Reputation: 116266

The reason is, references can't be reassigned.

You assigned the reference d to the first element in db:

data& d = db.get( 0 );

Then later you tried to reassign it:

d = db.get( 1 );

However, this doesn't change the reference itself, instead it changes the value the reference points to.

However, in this case the reference is to an abstract base class which contains no data. So the assignment isn't changing anything.

In effect you print the first element in db twice, then the 2nd element again twice.

Upvotes: 2

EddieBytes
EddieBytes

Reputation: 1343

Following lines might not be what you think: data& d = db.get( 0 ); std::cout << d.get_value() << std::endl; d = db.get( 1 );

the third line, d does not become a reference to the second database entry. It is still a reference to the first. References can only be initialized on creation.

Upvotes: 0

Cheers and hth. - Alf
Cheers and hth. - Alf

Reputation: 145279

In

data& d = db.get( 0 );
std::cout << d.get_value() << std::endl;
d = db.get( 1 );
std::cout << d.get_value() << std::endl;

the first statement is a reference initialization, while the third statement is a slicing assignment.

You can not re-seat a reference.

Cheers & hth.,

Upvotes: 4

Related Questions