Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Detect assignment of base class to reference pointing at derived class

I'm currently investigating the interplay between polymorphic types and assignment operations. My main concern is whether or not someone might try assigning the value of a base class to an object of a derived class, which would cause problems.

From this answer I learned that the assignment operator of the base class is always hidden by the implicitely defined assignment operator of the derived class. So for assignment to a simple variable, incorrect types will cause compiler errors. However, this is not true if the assignment occurs via a reference:

class A { public: int a; };
class B : public A { public: int b; };
int main() {
  A a; a.a = 1;
  B b; b.a = 2; b.b = 3;
  // b = a; // good: won't compile
  A& c = b;
  c = a; // bad: inconcistent assignment
  return b.a*10 + b.b; // returns 13
}

This form of assignment would likely lead to inconcistent object state, however there is no compiler warning and the code looks non-evil to me at first glance.

Is there any established idiom to detect such issues?

I guess I only can hope for run-time detection, throwing an exception if I find such an invalid assignment. The best approach I can think of just now is a user-defined assigment operator in the base class, which uses run-time type information to ensure that this is actually a pointer to an instance of base, not to a derived class, and then does a manual member-by-member copy. This sounds like a lot of overhead, and severely impact code readability. Is there something easier?

Edit: Since the applicability of some approaches seems to depend on what I want to do, here are some details.

I have two mathematical concepts, say ring and field. Every field is a ring, but not conversely. There are several implementations for each, and they share common base classes, namely AbstractRing and AbstractField, the latter derived from the former. Now I try to implement easy-to-write by-reference semantics based on std::shared_ptr. So my Ring class contains a std::shared_ptr<AbstractRing> holding its implementation, and a bunch of methods forwarding to that. I'd like to write Field as inheriting from Ring so I don't have to repeat those methods. The methods specific to a field would simply cast the pointer to AbstractField, and I'd like to do that cast statically. I can ensure that the pointer is actually an AbstractField at construction, but I'm worried that someone will assign a Ring to a Ring& which is actually a Field, thus breaking my assumed invariant about the contained shared pointer.

like image 373
MvG Avatar asked Aug 28 '14 10:08

MvG


People also ask

Can we assign base class to derived class pointer?

similarly a derived object is a base class object (as it's a sub class), so it can be pointed to by a base class pointer. However, a base class object is not a derived class object so it can't be assigned to a derived class pointer.

How the base class reference can point to derived class object what are its advantages?

Conclusion: A pointer to derived class is a pointer of base class pointing to derived class, but it will hold its aspect. This pointer of base class will be able to temper functions and variables of its own class and can still point to derived class object.

What happens when a reference to a derived class object is assigned to a base class reference?

Moreover, Object slicing happens when a derived class object is assigned to a base class object, and additional attributes of a derived class object are sliced off to form the base class object.

How do you access base class members in a derived class?

You can use both a structure and a class as base classes in the base list of a derived class declaration: If the derived class is declared with the keyword class , the default access specifier in its base list specifiers is private .


1 Answers

Since the assignment to a downcast type reference can't be detected at compile time I would suggest a dynamic solution. It's an unusual case and I'd usually be against this, but using a virtual assignment operator might be required.

class Ring {
    virtual Ring& operator = ( const Ring& ring ) {
         /* Do ring assignment stuff. */
         return *this;
    }
};

class Field {
    virtual Ring& operator = ( const Ring& ring ) {
        /* Trying to assign a Ring to a Field. */
        throw someTypeError();
    }

    virtual Field& operator = ( const Field& field ) {
        /* Allow assignment of complete fields. */
        return *this;
    }
};

This is probably the most sensible approach.

An alternative may be to create a template class for references that can keep track of this and simply forbid the usage of basic pointers * and references &. A templated solution may be trickier to implement correctly but would allow static typechecking that forbids the downcast. Here's a basic version that at least for me correctly gives a compilation error with "noDerivs( b )" being the origin of the error, using GCC 4.8 and the -std=c++11 flag (for static_assert).

#include <type_traits>

template<class T>
struct CompleteRef {
    T& ref;

    template<class S>
    CompleteRef( S& ref ) : ref( ref ) {
        static_assert( std::is_same<T,S>::value, "Downcasting not allowed" );
        }

    T& get() const { return ref; }
    };

class A { int a; };
class B : public A { int b; };

void noDerivs( CompleteRef<A> a_ref ) {
    A& a = a_ref.get();
}

int main() {
    A a;
    B b;
    noDerivs( a );
    noDerivs( b );
    return 0;
}

This specific template can still be fooled if the user first creates a reference of his own and passes that as an argument. In the end, guarding your users from doing stupid things is an hopeless endeavor. Sometimes all you can do is give a fair warning and present a detailed best-practice documentation.

like image 122
Zoomulator Avatar answered Sep 20 '22 11:09

Zoomulator