如何设计一个安全的重写工厂方法,允许子类返回协变类型?

huangapple go评论76阅读模式
英文:

How to design an override-safe factory method that allows subclasses to return covariant types?

问题

我有一个设计成具备const安全性的父类,因此我想创建“工厂”方法,以返回一个新构造的对象中的自身的修改版本。我希望能够通过值返回并避免堆分配,类似于std::string::substr的功能。在父类中这样做没有问题,但如何允许子类覆盖此方法以返回协变类型,最好不使用指针或引用?

我有一个给定的表示二维位置的Point类:

class Point
{
    double x, y;

public:
    Point() : Point(0, 0)
    { }

    Point(double xpos, double ypos) : x{xpos}, y{ypos}
    { }

    double get_x() const { return x; }
    double get_y() const { return y; }

    virtual Point flip_x() const
    {
        return Point{x, -y};
    }

    virtual Point flip_y() const
    {
        return Point{-x, y};
    }
    ...
};

注意,flip_xflip_y是工厂方法,它们通过值返回一个新的翻转后的Point对象。这允许我执行类似const Point flipped = p.flip_x()的操作,而不修改const Point p的内容。

我正在实现一个名为Point3DPoint子类,它支持额外的z深度坐标。我希望能够覆盖这些翻转函数以返回一个Point3D,以与flip_z的返回类型保持一致:

class Point3D : public Point
{
    double z;

public:
    Point3D() : Point3D(0, 0, 0)
    { }

    Point3D(double xpos, double ypos, double zpos)
    : Point(xpos, ypos), z{zpos}
    { }

    double get_z() const { return z; }

    // *** 错误:返回类型不是协变的 ***
    Point3D flip_x() const override
    {
        return Point3D{get_x(), -get_y(), -z};
    }

    // *** 错误:返回类型不是协变的 ***
    Point3D flip_y() const override
    {
        return Point3D{-get_x(), get_y(), -z};
    }
    
    Point3D flip_z() const
    {
        return Point3D{-get_x(), -get_y(), z};
    }
    ...
};

因此,这些错误告诉我返回类型与父类方法不协变。据我所知,使用协变返回的唯一选项是使用引用或指针。我不能返回对局部变量的引用或指针,而使用new返回堆分配的Point会增加不必要的内存管理复杂性。如何在返回协变类型时实现按值/静态绑定的效果,以使以下操作仍然有效:

const Point p{2, 2};
const Point flipped = p.flip_x();     // (2, -2)
const Point3D p3{2, 2, 2};
const Point3D flipped3 = p3.flip_x(); // (2, -2, -2)

在这个父类和这些限制内是否可能实现这个效果,还是我必须切换到指针和堆分配?

英文:

I have a parent class that is designed to be const-safe, so I want to create "factory" methods that will return an altered version of itself in a newly constructed object. I want to be able to return by value and avoid heap allocation, similar in function to std::string::substr. This works fine in the parent class, but how to I allow a subclass to return override this method to return a covariant type, preferably without pointers or references?

I have a Point class that is provided to me that represents a 2D position:

class Point
{
    double x, y;

public:
    Point() : Point(0, 0)
    { }

    Point(double xpos, double ypos) : x{xpos}, y{ypos}
    { }

    double get_x() const { return x; }
    double get_y() const { return y; }

    virtual Point flip_x() const
    {
        return Point{x, -y};
    }

    virtual Point flip_y() const
    {
        return Point{-x, y};
    }
    ...
};

Notice that flip_x and flip_y are factory methods that return a new flipped Point object by value. This allows me to do something like const Point flipped = p.flip_x() without modifying the contents of const Point p.

I am implementing a subclass to Point called Point3D that supports an additional z depth coordinate. I want to be able to override these flip functions to return a Point3D instead, in order to be consistent with the return type of flip_z:

class Point3D : public Point
{
    double z;

public:
    Point3D() : Point3D(0, 0, 0)
    { }

    Point3D(double xpos, double ypos, double zpos)
    : Point(xpos, ypos), z{zpos}
    { }

    double get_z() const { return z; }

    // *** ERROR: return type is not covariant ***
    Point3D flip_x() const override
    {
        return Point3D{get_x(), -get_y(), -z};
    }

    // *** ERROR: return type is not covariant ***
    Point3D flip_y() const override
    {
        return Point3D{-get_x(), get_y(), -z};
    }
    
    Point3D flip_z() const
    {
        return Point3D{-get_x(), -get_y(), z};
    }
    ...
};

So these errors tell me that the return types are not covariant with the parent methods. As far as I can tell, my only options for using covariant returns are to use references or pointers. I cannot return a reference or pointer to a local variable, and using new to return a heap allocated Point adds unnecessary complexity to memory management. How can I achieve the effect of returning by value/static binding when returning a covariant type, so that the following is still valid:

const Point p{2, 2};
const Point flipped = p.flip_x();     // (2, -2)
const Point3D p3{2, 2, 2};
const Point3D flipped3 = p3.flip_x(); // (2, -2, -2)

Is this possible using this parent class and within these limitations, or will I have to switch to pointers & heap allocation?

答案1

得分: 3

我不认为Point3D应该派生自Point,因为3D点不是2D点概念的特例。

如果你有两个不同的类,你就不会有这个问题。

如果你想从同一个源生成这些类,你可以这样做:

template<size_t N>
class Point {
    std::array<double, N> dims;

public:
    // ...
};

然后你就会有一个适用于任何大小点的通用实现。你不会有名为xyz的访问器,但你可以使用数值索引来指定分量。

英文:

I don't think Point3D should be derived from Point since a 3D point isn't a specialization of the concept of 2D point.

If you had two different classes, you wouldn't have this problem.

If you wanted to generate those classes from the same source, you could do something like:

template &lt;size_t N&gt;
class Point {
   std::array&lt; double, N &gt; dims;

public:
   ...
};

and then you'd have a common implementation for points of any size. You wouldn't have accessors called x, y, and z, but you could use numeric indices to specify components.

答案2

得分: 1

如果您移除虚拟关键字,您可以这样做:

class Point
{
    double x, y;

public:
    Point() : Point(0, 0) { }
    Point(double xpos, double ypos) : x{xpos}, y{ypos} { }

    double get_x() const { return x; }
    double get_y() const { return y; }

    Point flip_x() const { return Point{x, -y}; }
    Point flip_y() const { return Point{-x, y}; }
    // ...
};

class Point3D : public Point
{
    double z;

public:
    Point3D() : Point3D(0, 0, 0) { }
    Point3D(double xpos, double ypos, double zpos) : Point(xpos, ypos), z{zpos}     {}

    double get_z() const { return z; }

    // 隐藏父类的方法
    Point3D flip_x() const { return Point3D{get_x(), -get_y(), -z}; }
    Point3D flip_y() const { return Point3D{-get_x(), get_y(), -z}; }
    
    Point3D flip_z() const { return Point3D{-get_x(), -get_y(), z}; }
    // ...
};
英文:

If you remove virtual, you might do:

class Point
{
    double x, y;

public:
    Point() : Point(0, 0) { }
    Point(double xpos, double ypos) : x{xpos}, y{ypos} { }

    double get_x() const { return x; }
    double get_y() const { return y; }

    Point flip_x() const { return Point{x, -y}; }
    Point flip_y() const { return Point{-x, y}; }
    // ...
};

class Point3D : public Point
{
    double z;

public:
    Point3D() : Point3D(0, 0, 0) { }
    Point3D(double xpos, double ypos, double zpos) : Point(xpos, ypos), z{zpos}     {}

    double get_z() const { return z; }

    // hides parent ones
    Point3D flip_x() const { return Point3D{get_x(), -get_y(), -z}; }
    Point3D flip_y() const { return Point3D{-get_x(), get_y(), -z}; }
    
    Point3D flip_z() const { return Point3D{-get_x(), -get_y(), z}; }
    ///...
};

huangapple
  • 本文由 发表于 2023年6月29日 02:13:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/76575727.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定