C ++为什么我不能将基类分配给子类?

时间:2010-02-11 02:27:54

标签: c++ inheritance

我有这样的代码:

class Base
{
public:
  void operator = (const Base& base_)
  {
  }
};

class Child : public Base
{
public:

};

void func()
{
  const Base base;
  Child child;
  child = base;
}

我的问题是:既然Child派生自Base(因此它应该继承Base的operator =),那么语句怎么来的

child = base;

被执行,我得到一个像这样的编译器错误:

>.\main.cpp(78) : error C2679: binary '=' : no operator found which takes a right-hand operand of type 'const Base' (or there is no acceptable conversion)
1>        .\main.cpp(69): could be 'Child &Child::operator =(const Child &)'
1>        while trying to match the argument list '(Child, const Base)'

我想要的行为是让Child类认识到它被分配了一个Base类,并且只是“自动”调用它的父类的operator =。

我将此代码添加到Child类

void operator = (const Base& base_)
{
  Base::operator=(base_);
}

然后一切都编好了。虽然我不认为这会很好,因为如果我有5个不同的类继承自Base,那么我必须在每个派生类中重复相同的代码。

注意:我将Base复制到Child的意图是简单地将 common 的成员复制到Base }和Child(这将是Base的所有成员)。即使在阅读了下面的所有答案之后,我也真的不明白为什么C ++不允许这样做,特别是如果在operator=类中定义了明确的Base

8 个答案:

答案 0 :(得分:13)

该标准在12.8 / 10“复制类对象”中提供了您的具体问题的原因(强调添加):

  

因为如果没有由用户声明,为类隐式声明了复制赋值运算符,基类复制赋值运算符总是被派生类的复制赋值运算符隐藏(13.5.3 )。

因此,当编译器对operator=(const Child&)执行名称查找/重载解析时,会隐式声明Child::operator=(),所以Base类的函数签名从未被考虑过(隐藏)

答案 1 :(得分:8)

下面的代码是我从一开始就想要的行为,它编译,

class Base
{
public:
  void operator = ( const Base& base_)
  {
  }
};

class Child : public Base
{
};

void func()
{
  const Base base;

  Child child;

  child.Base::operator=(base);
}

我从来不知道你可以明确地称之为:

  child.Base::operator=(base);

无论如何,我学到了很多东西。感谢所有在此发布答案的人。

答案 2 :(得分:6)

您不能将Base指定给Child,因为Child可能不是base。要使用典型的动物示例,您在此处拥有:

const Animal animal;
Dog dog;
dog = animal;

但是动物可能是一只猫,你肯定不能把猫分配给狗。

话虽如此,你不可能反过来这样做:

const Dog dog;
Animal animal;
animal = dog;

狗肯定是一种动物,但你有一个尺寸问题。 animal占用了sizeof(Animal)空间,但dog占用sizeof(Dog)空间,这两个数量可能不同。

在C ++中使用多态时,您需要使用引用或指针。例如,以下情况很好:

Dog* const dog;
Animal* animal;
animal = dog;

这里,无论指向什么,指针的大小都是相同的,因此可以进行分配。请注意,您仍需要维护正确的层次结构转换。即使使用指针,也不能将Base指定给Child,因为我之前解释的原因是:Base可能与Child完全不同。

答案 3 :(得分:2)

因为基地不是孩子。考虑:

class Child : public Base 
{ 
public: 
   SomeFunc();
};

void func() 
{ 
  const Base base; 
  Child child; 
  child = base; 
  child.SomeFunc();     // whatcha gonna call?
} 

更新:要回答评论中的问题,child.SomeFunc();完全合法 - 如果child的值不是真正的Child对象,那么这只是一个问题。编译器不允许child = base;,因为它会造成合法调用失败的情况。

答案 4 :(得分:2)

这实际上不是一个答案,但是如何回答问题。这就是为什么 - 不是。

类应该意味着什么。关于类中任何格式良好的对象(“类不变量”),应该有一些有用的东西,因为这样你可以根据类的工作原理来推理程序。如果一个类只是数据成员的集合,则不能这样做,并且必须根据每个单独的数据元素来推断该程序。

建议的赋值运算符将更改基类中的每个数据成员,但不会触及子类中定义的成员。这意味着两件事之一。

如果在将另一个Base对象转储到其Base组件后,Child对象格式正确,则意味着其他数据成员与Base数据成员没有真正的连接。在这种情况下,Child实际上不是Base的子类型,公共继承是错误的关系。儿童与基地没有“是一种”关系,而是与“有一种”关系。这通常由组合表示(Child具有Base数据成员),也可以由私有继承表示。

如果在将另一个Base对象转储到其Base组件后,Child对象的格式不正确,那么建议的赋值运算符是一种非常快速且方便的方法来处理变量。

举个例子,让我们有一个哺乳动物基类和两个子类,Cetacean和Bat。动物有大小和体重之类的东西,而儿童班则有与他们吃什么以及如何移动有关的领域。我们将鲸类分配给哺乳动物,这是合理的(所有鲸类特定的领域都被丢弃),现在我们将该哺乳动物分配给蝙蝠。突然间,我们有一个二十吨的蝙蝠。

使用一组设计良好的类,我能想到的这个赋值运算符的唯一方法是,如果子类没有其他数据成员,只有其他行为,即使这样也是可疑的。

因此,我唯一的建议是重做你的班级设计。许多人,特别是那些熟悉Java和其他倾向于深度继承层次结构的语言的人,在C ++中使用的继承太多了。 C ++通常最适用于大量基类和相对较浅的层次结构。

答案 5 :(得分:1)

如果未明确声明复制赋值,C ++编译器将为您创建复制赋值。在你的情况下,它将是

class Child : public Base
{
public:
Child& operator=(const Child& rhs) { ... }

};

这就是你得到这个编译错误的原因。此外,当您提供复制赋值时,返回对自身的引用更有意义,因为它允许您将操作符链接在一起。

宣布像这样的副本分配不是一个好主意。

void operator = (const Base& base_)
{
  Base::operator=(base_);
}

我不明白你为什么只想在复制作业时复制基类部分。

答案 6 :(得分:1)

答案比我想象的更可怕。 你可以这样做。标准的平均值在7.3.3节中有一点注意 主要问题是在Child中默认定义的operator =隐藏了operator = from Base,因此你需要使用“using”将它导回到类范围。

这对我编译好。但是我发现这个想法非常可怕,如果Child中的内容需要初始化,可能会充满未定义的行为。

class Base
{
public:
  void operator = (const Base& base_)
  {
  }
};

class Child : public Base
{
public:
using Base::operator=;

};

int main()
{
  const Base base = Base();
  Child child;
  child = base;
}

编辑:再次制作基本const并避免“未初始化的const”错误。

答案 7 :(得分:0)

问题是您无法为非常量变量分配常量值。如果可以,您可以通过修改非常量变量来修改常量值。

基础是const是必要的吗?我问,因为你真的遇到了两个问题:const正确性和继承性/多态性。

编辑:我现在第二次猜测自己的第一个声明。有任何澄清吗?