开发者

C++ Implicit Conversion Operators

开发者 https://www.devze.com 2022-12-23 08:02 出处:网络
I\'m trying to find a nice inheritance solution in C++. I have a Rectangle class and a Square class. The Square class can\'t publicly inherit from Rectangle, because it cannot completely fulfill the

I'm trying to find a nice inheritance solution in C++.

I have a Rectangle class and a Square class. The Square class can't publicly inherit from Rectangle, because it cannot completely fulfill the rectangle's requirements. For example, a Rectangle can have it's width and height each set separately, and this of course is impossible with a Square.

So, my dilemma. Square obviously will share a lot of code with Rectangle; they are qui开发者_如何学编程te similar.

For examlpe, if I have a function like:

bool IsPointInRectangle(const Rectangle& rect);

it should work for a square too. In fact, I have a ton of such functions.

So in making my Square class, I figured I would use private inheritance with a publicly accessible Rectangle conversion operator. So my square class looks like:

class Square : private Rectangle
{
    public:
        operator const Rectangle&() const;
};

However, when I try to pass a Square to the IsPointInRectangle function, my compiler just complains that "Rectangle is an inaccessible base" in that context. I expect it to notice the Rectangle operator and use that instead.

Is what I'm trying to do even possible?

If this can't work I'm probably going to refactor part of Rectangle into MutableRectangle class.

Thanks.


You can make a class ImmutableRectangle, without any mutators and with only const methods, from which you can correctly derive both Rectangle, and, separately, ImmutableSquare and, from that, Square. Note that, shorn of mutability, the IS-A relationship does hold -- an immutable square IS-A immutable rectangle: mutability is the only serious issue, so by factoring it out you can get some substantial code reuse (for all const uses -- ones that don't in fact use, or need, mutability).

Introducing mutability along the inheritance is OK as long as no class invariants of the (immutable) base actually rely on the immutability characteristic; and of course an immutable object can be properly constructed from a const pointer or reference to the mutable version (presumably in a separate inline friend function to avoid giving the base class a dependency on the derived class;-) for reasonably-handy use.

Edit: one comment understandably expresses qualms because "a mutabe is not an immutable": to reason about this, you need to understand what "IS-A" means... and it does not mean the Korzybski-denied "is of identity": it means the LSP. Go through the rigmarole of constraints this means: covariance, contravariance, weaker-equal preconditions, stronger-equal postconditions, etc, as they apply to the const methods of the base (immutable) and derived (mutable) classes. You'll see that class invariants are the only issue, as I mentioned in the previous paragraph, so just avoid asserting immutability as a class invariant and you're in clover;-).

Maybe it would help to name the base class NotNecessarilyMutableRectangle since it doesn't assert immutability as a class invariant; that very precise naming might be philosophically reassuring but perhaps a trifle un-handy in everyday coding.


Well, I'm surprised. It seems privately inheriting a class A prevents you from using operator A outside the class.

You can solve your problem by making a member Rectangle for square and using it for the cast:

class Square {
    Rectangle r;
    public:
        operator const Rectangle&() const {
            return r;
        }
};

This should compile and work. And I believe it won't give you that much more work to do if any.


I believe, though I'm not certain, that you have to use an explicit cast to invoke that conversion operator in that context. The ImmutableRectangle base is a common and effective solution. Quite similarly, you can use a more abstract solution such as:

/**
 * Base for rectangular classes; name it whatever you want.
 */
class RectangularBase {
public:

    virtual unsigned int getValue(int) const = 0;

};

/**
 * Base for specific rectangular classes; also named whatever.
 */
template<unsigned int Dimensions>
class Rectangular : public RectangularBase {
public:

    virtual unsigned int getValue(int index) const { return values[index]; }

private:

    unsigned int values[Dimensions];

};

/**
 * A square is Rectangular but has only one significant dimension.
 */
class Square : public Rectangular<1> {
public:

    unsigned int getSideLength() const { return getValue(0); }

};

/**
 * A Rectangle is Rectangular and has two significant dimensions.
 */
class Rectangle : public Rectangular<2> {
public:

    unsigned int getWidth() const { return getValue(0); }
    unsigned int getHeight() const { return getValue(1); }

};
0

精彩评论

暂无评论...
验证码 换一张
取 消