David Doria
David Doria

Reputation: 10273

Disallow slicing to keep multiple parent class properties in sync

I am deriving an object from two parent classes. These two parents each have different types of properties, but I want the child to keep them in sync with each other. However, I want to disallow users of the library from treating Child like a ParentA or a ParentB accidentally via slicing. For example:

#include <iostream>

class ParentA
{
public:
    void modify()
    {
        std::cout << "modifyA" << std::endl;
    }

    void readA()
    {
        std::cout << "readA" << std::endl;
    }
};

class ParentB
{
public:
    void modify()
    {
        std::cout << "modifyB" << std::endl;
    }
    void readB()
    {
        std::cout << "readB" << std::endl;
    }
};

class Child : public ParentA, public ParentB
{
public:
    void modify()
    {
        // Do some bounds checking to make sure ParentA and ParentB stay in sync, then:
        ParentA::modify();
        ParentB::modify();
        std::cout << "modifyChild" << std::endl;
    }
};

void Change(ParentA object)
{
    object.modify();
}

int main()
{
    std::cout << "This is standard:" << std::endl;
    ParentA parentA;
    parentA.modify();

    ParentB parentB;
    parentB.modify();

    Child child;
    child.readA();
    child.readB();
    child.modify();

    std::cout << "Want to avoid this:" << std::endl;
    Change(child);

    return 0;
}

This call to Change(child); calls ParentA's modify() function, in which the ParentA properties can get out of sync with the ParentB properties, leaving the Child in a bad state.

There are many functions (the read*() ones here) in ParentA and ParentB that I don't want to have to manually forward from Child, so I can't derive privately.

Is there a way to make this call to Change(child) produce a compiler error (without changing the signature of Change)?

Upvotes: 1

Views: 63

Answers (2)

Simon Kraemer
Simon Kraemer

Reputation: 5690

As the comments already say the cleanest way might be to just inherit from ParentA and ParentB with private and forward the needed functions.

I had another idea: You could extract the functionality of ParentA and ParentB into 2 abstract classes (AbstractParentA,AbstractParentB) and use these classes as base classes.

This would give you the desired behaviour:

#include <iostream>

class AbstractParentA
{
    virtual void no_instance() = 0;

public:
    void modify()
    {
        std::cout << "modifyA" << std::endl;
    }

    void readA()
    {
        std::cout << "readA" << std::endl;
    }
};

class AbstractParentB
{
    virtual void no_instance() = 0;

public:
    void modify()
    {
        std::cout << "modifyB" << std::endl;
    }
    void readB()
    {
        std::cout << "readB" << std::endl;
    }
};


class ParentA : public AbstractParentA
{
    virtual void no_instance() override {}
};


class ParentB : public AbstractParentB
{
    virtual void no_instance() override {}
};

class Child : public AbstractParentA, public AbstractParentB
{
    virtual void no_instance() override {}

public:
    void modify()
    {
        // Do some bounds checking to make sure ParentA and ParentB stay in sync, then:
        AbstractParentA::modify();
        AbstractParentB::modify();
        std::cout << "modifyChild" << std::endl;
    }
};

void Change(ParentA object)
{
    object.modify();
}

int main()
{
    std::cout << "This is standard:" << std::endl;
    ParentA parentA;
    parentA.modify();

    ParentB parentB;
    parentB.modify();

    Child child;
    child.readA();
    child.readB();
    child.modify();

    std::cout << "Want to avoid this:" << std::endl;
    Change(child);

    return 0;
}

error C2664: 'void Change(ParentA)': cannot convert argument 1 from 'Child'

note: No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called

Upvotes: 1

Mark B
Mark B

Reputation: 96311

There is in fact a way to do this (although said you didn't like it): private or protected inheritance is the C++ mechanism to achieve what you want.

Bear in mind that since your child class is trying to keep some sort of invariant between A and B, if you inherit publicly, someone will find a way to use A or B's interface to violate the invariant anyway so you need to protect against those being used in the child directly, which the restricted inheritance does perfectly.

If there are then some methods in the parent that don't affect the two-class invariant you can using those down into the public section of Child.

Upvotes: 2

Related Questions