C++ 可以不继承成员吗?

C++ is it possible to NOT inherit a member?

我很确定不是,但我还是要问...
这是一个例子: 假设我有一个名为 Elipse:

的 class
class Elipse
{
private:
  double d_m_lAxis; // the length of the large Axis of the elipse
  double d_m_sAxis; // short axis
//Point is a struct of 2 doubles (x and y)
  Point P_m_focal1; //focal point 1
  Point P_m_focal2; //focal point 2
protected:
  Point P_m_center; 

public:
  Elipse(Point _P_lowerLeft,Point _L_upperRight)
  {
    //draw elipse in a rectangle, just like in paint
  }
}

现在我想从Elipse继承Circle(因为Circle是一个Elipse,两个Axis的长度相同):

class Circle : public Elipse   
{
private:
  double d_m_radius;
public:
  Circle(Point _P_lowerLeft,double _d_diameter) :
    Elipse(_P_lowerLeft,/*get the upper ight*/
      Point(_P_lowerLeft.x + _d_diameter,_P_lowerLeft.y + _d_diameter))
      {
        // draw a circle in a square, just like in paint
      }
}

所以现在我得到了我想要的 class,它无法访问 Elipse 的任何私有成员,这在 Circle 中会变得多余。
但是,据我所知,这些成员 still 继承并且 child class 根本无法访问它们。这使得 class 的大小变得不必要地大,并且也可以通过从 Elipse 继承的函数访问这些成员,除非它们被覆盖。

有没有办法继承成员?

您试图通过此继承实现的目标违反了 Liskov 替换原则。事实上,在你的情况下,Ellipse 不应该是 Circle 的基础。查看更多详细信息,例如在维基百科上:

https://en.wikipedia.org/wiki/Liskov_substitution_principle#A_typical_violation - 这里是根据非常相似的例子(矩形和正方形)进行解释

圆是椭圆的特例,不是它的子类型,因此您不需要这样做。相反,只需为任何轴使用相同的值初始化椭圆。

关于继承,private、protected成员就是为了这个目的。他们喜欢将被继承的私人。由于基础 class 私有成员,class 不会很大。编译器会优化最终代码。

This makes the class unnecessarily large in size and it is also possible to access those members via functions inherited from Elipse, unless they are overriden. Is there a way to NOT inherit a member at all?

不过,您可能需要重新考虑您的设计, 您可以利用 EBO(空基 class 优化),不会有大小开销。

有一个空类型,如:

class EllipticalType {};

然后像

一样定义你的椭圆
class Elipse : public EllipticalType
{
private:
  double d_m_lAxis; // the length of the large Axis of the elipse
  double d_m_sAxis; // short axis
//Point is a struct of 2 doubles (x and y)
  Point P_m_focal1; //focal point 1
  Point P_m_focal2; //focal point 2
protected:
  Point P_m_center; 

public:
  Elipse(Point _P_lowerLeft,Point _L_upperRight)
  {
    //draw elipse in a rectangle, just like in paint
  }
}

然后你做:

class Circle : public EllipticalType
{
          private:
            double d_m_radius;
          public:
    Circle(Point _P_lowerLeft,double _d_diameter) : Elipse(_P_lowerLeft,/*get the upper right*/Point(_P_lowerLeft.x + _d_diameter,_P_lowerLeft.y + _d_diameter))
        {
        // draw a circle in a square, just like in paint
        }

}

也可以抽取普通成员放入EllipticalType

没有。你不能。 C++ 中的继承在实践中意味着一定程度的二进制兼容性。

解决方法如下。但首先:

圆不是椭圆。

更准确地说,在许多有用的方面,可变圆并不是可变椭圆。您可以在可变椭圆上执行一些操作,其后置条件与可变圆上的相同操作不一致。

例如,假设有get/set major/minor轴。 set major axis 的合理后置条件是 get minor axis 在可变椭圆上保持不变。虽然您可以在圆上设置 major/minor 轴,但后置条件不能成立!

现在,一个不变的圆是一个不变的椭圆。只有在您开始编辑后才会出现 covariant/contravariant 问题。此问题不限于rectangle/square、ellipse/circle、automobile/truck风格"toy" OO;同样,Derived 的可编辑列表也不是 Base 的可编辑列表。 Base 的可编辑列表接受 any Base 类型; Derived 列表并非如此。

另一方面,Derived 的不可变列表是 Base 的不可变列表。


回到你原来的问题,继承这样的实现可能是不明智的。但是,您可以将数据从接口的逻辑中分离出来。

然后在不关联数据的情况下继承接口和逻辑。

在 C++ 中,这可以通过模板和 CRTP 来完成。如果您不了解 CRTP,则以下内容将是晦涩难懂的。我不保证反阳性。

template<class Self>
struct ellipse_math {
  Self* self(){ return static_cast<Self*>(this);}
  Self const* self() const { return static_cast<Self const*>(this);}

  double get_area() const { /* math using self()->get_major() and self()->get_minor(); */ }
};
struct circle_state {
  double m_radius;
  double get_major() const;
  double get_minor() const;
  double get_radius() const;
};
struct ellipse_state {
  double m_major, m_minor;
  double get_major() const;
  double get_minor() const;
};
struct I_ellipse {
  virtual double major()const=0;
  virtual double minor()const=0;
  cirtual double area()const=0;
  virtual ~I_ellipse(){};
};
struct I_circle:I_ellipse {
  virtual double radius()const=0;
};
template<class Self, class I=I_ellipse>
struct impl_I_ellipse : I  {
  Self* self(){ return static_cast<Self*>(this);}
  Self const* self() const { return static_cast<Self const*>(this);}
  virtual double major()const final override { return self()->get_major(); }
  virtual double minor()const final override { return self()->get_minor(); }
  virtual double area()const final override { return self()->get_area(); }
};
template<class Self, class I=I_circle>
struct impl_I_circle : impl_I_ellipse<Self, I> {
  Self* self(){ return static_cast<Self*>(this);}
  Self const* self() const { return static_cast<Self const*>(this);}
  virtual double radius()const final override { return self()->get_radius(); }
};

struct ellipse :
  ellipse_state,
  impl_I_ellpise<ellipse>,
  ellpise_math<ellipse>
{};
struct circle  :
  circle_state,
  impl_I_circle<circle>,
  ellpise_math<circle>
{};

在这个简单的例子中,这非常荒谬,但我确实避免了在我的圆形实现中使用未使用的椭圆字段,并且必须重用椭圆逻辑。

以上内容是在 phone 上输入的,因此可能包含 tpyos。

老实说,如果我要走这么远,我会使用免费函数进行分派,一直使用 SBO 类型擦除类椭圆和类圆对象。因为上面的继承和 vtable 比它们的帮助更多地阻碍了。

数学措辞 "a circle is an ellipse where [...]" 应改写为更能识别类型的 "For every circle, there exists an ellipse where [...], that describes the same set of points".

考虑到这一点,您的实现应该允许从 Circle 个对象创建 Ellipse 个对象,但不允许多态替换。
一种可能性是显式转换运算符:

class Elipse
{
  // as you defined
};

class Circle
{
  // As you defined
  explicit operator Ellipse()
  {
    Point lower_left, upper_right;
    // calculate the two points
    return Ellipse(lower_left, upper_right);
  }
};