C++ 用超类实例覆盖子类实例

C++ 用超类实例覆盖子类实例,c++,types,casting,C++,Types,Casting,为了解决一些问题(实际上是在Ada中),我想出了以下代码。为什么它是合法的 class Superclass { public: virtual void Announce() { printf("I am the superclass\n"); } }; class Subclass : public Superclass { public: void Announce() { printf("I am the subclass\n")

为了解决一些问题(实际上是在Ada中),我想出了以下代码。为什么它是合法的

class Superclass {
public:
    virtual void Announce() {
        printf("I am the superclass\n");
    }
};

class Subclass : public Superclass {
public:
    void Announce() {
        printf("I am the subclass\n");
    }
};

int main() {
    Superclass osuper;
    Subclass osub;

    Superclass* p = &osub;
    *p = osuper;
    osub.Announce();

    return 0;
}
main()
中,我创建了
子类的一个实例
,然后用
超类的一个实例物理覆盖它。然后,我成功地在被覆盖(因此损坏)的对象上调用了
子类的方法

我不能直接分配
osub=osuper
,因为这没有意义,但通过指针我似乎绕过了它。上面的代码编译得很好,没有警告,但是当我调用
osub.Announce()
时,
osub
中的内存不再包含有效的
子类
对象


这不可能是类型安全的(或者,甚至是通常安全的),但是编译器似乎非常满意。为什么?

在对象上使用=运算符不是无效的,并且不会覆盖任何内容,将使用默认的复制构造函数。 如果您想真正覆盖内存中的对象,请尝试memcpy(p,&osub,sizeof(Superclass))。现在这将是一个覆盖:)

请看

这种需求很奇怪。 当调用子类实例的构造函数时,将调用超类的构造函数来初始化成员变量。 但是,如果您仍需要此功能,可以实现如下成员函数:

Subclass& copy(const Superclass& superobject);
您正在调用复制分配运算符,而不是“物理覆盖”。这是一个函数,与构造函数之外的任何其他函数一样,它可以修改已经存在的对象的成员,这不是“魔法”

您的对象没有成员变量,因此隐式生成的
操作符=
调用是禁止操作的。不存在“损坏的对象”<代码>osub.Announce()
调用
Subclass::annound
,因为
osub
子类

实际调用的函数是
Superclass::operator=(Superclass const&)

函数
Subclass::operator=(Subclass const&)在重载解析中丢失,因为
oseper
未隐式转换为
子类const&


您可能得到的是,如果子类有成员变量,那么行
*p=osuper
可能使
osub
对象处于
子类的设计者不希望的状态

应该在返回超类的子类中放置一个方法。 超类getSuperClass(){ 超类sup; //在sup上复制内容; 返回sup;
}

关于为什么这样做的执行摘要似乎是:隐式复制构造函数讨厌你,想要伤害你

隐式复制构造函数看起来像是对对象进行逐字节复制,从而使assigned to对象与assigned from对象相同。然而,它并没有做到这一点。它只是复制成员。它尤其不会复制对象的vtable指针。作业可能会遇到同样的问题(尽管我还没有检查)。我对C++11移动一无所知

如果这违反了类层次结构的任何契约,或者以任何其他方式对类层次结构没有任何意义,那么基本上就是你

这个故事的寓意是:类应该总是从
boost::noncopyable
继承


(我知道复制构造函数是从C派生出来的。这不是一个借口。)

@Mattmcnab对,不是在那个特定的情况下,但一般来说,关于赋值运算符,我猜您指的是建议如何使用复制和交换习惯用法实现复制赋值运算符;如果是这样,则必须显式编写,隐式生成的复制赋值运算符永远不会这样做;它确实是memberwise赋值的。我正要说默认的复制构造函数相当于
memcpy()
——但这显然不是真的;默认复制构造函数不复制vtable指针!因此,如果在
p
上调用
annound()
,那么它将调用
Subclass::annound()
,即使
p
将包含
超类的实例的成员。那更坏了!为什么编译器让我这么做?@DavidGiven我不明白你的问题。为什么你认为编译器应该拒绝使用memcpy?编译器的目的不是试图理解代码并进行某种质量检查。如果您需要,请使用SCA工具。@glezmen抱歉,我不明白您在说什么。我没有使用memcpy();这才是重点,真的。我正在做一系列完全有效的操作,结果是垃圾。但我现在明白了原因;见下文。Re最后一段:是的,完全正确。恐怕你所说的其余部分实际上与我想知道的无关。除了“不要做”之外,我不知道有什么“解决办法”;这是一个类似于如何分割任何对象的问题(例如,
Derived;Base b;b=d;
),实际上,您不能这样做,因为编译器不会为派生对象生成一个隐式的副本构造函数,该构造函数取一个基。(显然,如果您添加一个自定义副本构造函数,您可以做任何事情。)另外,我不是在寻找解决方案;我在试图理解为什么允许这样做。你能建议一个不允许这样做的语言规则吗?即使你对赋值运算符做了一些特定的操作,也有人可以给
Base
一个函数
void f(){/*做一些派生运算符不希望的事情*/}
。然后,任何人都可以在您的
派生对象上调用
Base::f()
。明确地说,派生对象没有“损坏”在未定义行为的意义上;但是,如果派生对象的逻辑状态涉及以特定方式设置的一些基本变量,那么派生对象可能稍后会中断。我认为避免这种情况的唯一方法是使
Base<