能够将子类实例分配给堆栈框架中的基类变量的动机是什么?

时间:2015-03-26 21:47:33

标签: c++ stack polymorphism type-conversion

鉴于此代码:

#include <iostream>

class base {
private:
    char x;
public:
    base(char x) : x(x) {
        std::cout << "base::base(char) " << this << std::endl;
    }
    base(base&& rhs) {
        std::cout << "base::base(base&&), moving from " << &rhs << " to " << this << std::endl;
        x = rhs.x; rhs.x = '\0';
    }
    virtual ~base() {
        std::cout << "base::~base " << this << std::endl;
    }
};

class child : public base {
private:
    char y;
public:
    child(char x, char y) : base(x), y(y) {
        std::cout << "child::child(char, char) " << this << std::endl;
    }
    child(child&& rhs) : base(std::move(rhs)) {
        std::cout << "child::child(child&&), moving from " << &rhs << " to " << this << std::endl;
        y = rhs.y; rhs.y = '\0';
    }
    virtual ~child() {
        std::cout << "child::~child " << this << std::endl;
    }
};

int main(int argc, char* argv[]) {
    { // This block enables me to read destructor calls on the console...
        base o = child('a', 'b');
    }
    std::cin.get();
    return 0;
}

main的堆栈框架中,有child实例化的区域。之后,调用base的移动构造函数,并引用新创建的childbase的(移动)构造发生在同一堆栈框架中的不同区域,并复制child的{​​{1}}部分。从现在开始,base左侧的所有内容都是child部分,只能base

我知道堆栈中的对象不可能存在多态性,我想我理解为什么:效率。没有vtable查找等。编译器可以选择在编译时调用的方法。

我不明白的是:为什么在构成base的所有内容时,可以将child实例分配给base变量(在堆栈框架中)迷路?这是什么动机?我预计会出现编译错误或至少发出警告,因为我无法想出允许它的充分理由。

2 个答案:

答案 0 :(得分:0)

请注意,base o = child('a', 'b')实际上是在调用复制构造函数,因此您并没有真正破坏child对象,而是从o构建child < em>作为副本。您需要使用指针/引用来对一个底层对象进行多态访问。

child c = child('a', 'b');
base o = c; // at this point we have two objects. 

其他语言,例如Java和C#,在处理对象方面具有相似的语法,但它们实际上使用了引用,因此与我们在此处所做的完全不同。

此外,在某些情况下,基础和子类无论如何都具有相同的大小。一个激励性的案例是一个类,它只是int的一个包装器,它暴露了那些做位操作的成员来打包内部的东西。由于对象本身很小,因此按值移动它是有道理的。但是因为你可能只想查看原始int,所以将它转换为更原始的类型是有意义的,没有任何东西会丢失。

编辑: 好的,事实证明移动构造函数应该被调用,但是Visual Studio(我的编译器)还没有自动生成移动构造函数。

#include <iostream>
class Base
{
public:
    Base() {}
    Base(const Base& other) { std::cout << "Copy"; }
};

class Der : public Base{};

void main() {
    Base b = Der();
}

在Visual Studio中输出“复制”。但是如果添加自己的移动构造函数,则会调用它,因为Der()是一个r值。

请记住,移动构造函数是C ++的新增功能,因此在11之前,语法是允许的。因为它们必须支持传统的C ++语法,所以它们实际上无法阻止编译,因为它自添加移动构造函数以来并不总是有意义。这可能是问题的真正答案:遗留问题,因为在自动生成的移动构造函数中进行切片更具争议性。

答案 1 :(得分:0)

这对评论来说太长了,并且确实是答案的一部分:

我相信现在很明显你实际上是在创建一个新的base对象,而不是为变量分配一些现有的值(因为我们这里不是Java;要获得Java的C ++代码)行为就像std::shared_ptr<base> o = std::make_shared<child>('a','b');)。同样根据评论,您现在正在寻找允许为什么的动机。答案非常简单:

因为child公开来自base,也称为“is-a”关系,所以任何child对象,在字面意义上,都是 a (特殊种类)base个对象。在设计良好的类层次结构中,您可以在任何需要child对象的地方使用base对象( it 荣誉 界面 不变量)。这是一个重要的概念,这就是为什么它有一个名称: Liskov替代原则。(这也是为什么你在尘封的旧书中找到的关于类设计的例子{{1从Rectangle派生而Shape派生自Square是非常糟糕的例子:在软件设计中,“是一种”意味着/应该意味着与数学不同的东西。)

现在,您可以从Rectangle对象移动构造base对象,是吗?由于base对象 一个child对象,因此您也可以从base移动构建base。 (当然,除非你违反了公开衍生自child所建立的合同.C ++会给你一把机枪和胶带来消除你的脚,如果你愿意的话。)