鉴于此代码:
#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
的移动构造函数,并引用新创建的child
。 base
的(移动)构造发生在同一堆栈框架中的不同区域,并复制child
的{{1}}部分。从现在开始,base
左侧的所有内容都是child
部分,只能base
。
我知道堆栈中的对象不可能存在多态性,我想我理解为什么:效率。没有vtable查找等。编译器可以选择在编译时调用的方法。
我不明白的是:为什么在构成base
的所有内容时,可以将child
实例分配给base
变量(在堆栈框架中)迷路?这是什么动机?我预计会出现编译错误或至少发出警告,因为我无法想出允许它的充分理由。
答案 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 ++会给你一把机枪和胶带来消除你的脚,如果你愿意的话。)