能够将子 class 实例分配给堆栈帧中的基础 class 变量的动机是什么?
What's the motivation for being able to assign a child class instance to base class variable in a stack frame?
鉴于此代码:
#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 = '[=12=]';
}
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 = '[=12=]';
}
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
的(移动)构造发生在同一堆栈帧的不同区域,并复制从 base
派生的 child
的部分。从现在开始,child
剩下的就是 base
部分了,只剩下 base
.
我知道堆栈上的对象不可能实现多态性,我想我理解原因:效率。没有 vtable 查找等。编译器可以选择在编译时调用的方法。
我不明白的是:当构成 child
的所有内容时,为什么可以将 child
实例分配给 base
变量(在堆栈框架中)迷路?这样做的动机是什么?我期望编译错误或至少是警告,因为我想不出允许它的充分理由。
请记住 base o = child('a', 'b')
实际上是在调用复制构造函数,因此您并没有真正破坏 child
对象,而是从 [=13= 构造 o
] 作为副本。您需要使用 pointers/references 来对一个基础对象进行多态访问。
child c = child('a', 'b');
base o = c; // at this point we have two objects.
其他语言,例如 Java 和 C# 具有类似的处理对象的语法,但它们实际上使用引用,因此与我们在这里所做的完全不同。
此外,在某些情况下,base 和 subclass 无论如何都具有相同的大小。一个激励案例是 class,它只是 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中输出"Copy"。但是,如果您添加自己的移动构造函数,则会调用它,因为 Der() 是右值。
请记住,移动构造函数是 C++ 的新增功能,因此在 11 之前,该语法是允许的。由于它们必须支持遗留的 C++ 语法,因此它们实际上无法阻止它进行编译,仅仅因为它自从添加了移动构造函数以来并不总是那么有意义。这可能是问题的真正答案:遗留问题,因为在自动生成的移动构造函数中切片更值得商榷。
这对于评论来说太长了,实际上是答案的一部分:
我相信现在很明显您实际上是在创建一个新的 base
对象,而不是将一些现有值分配给变量(因为我们不在 Java 这里;C++获得 Java 行为的代码类似于 std::shared_ptr<base> o = std::make_shared<child>('a','b');
)。另外根据评论,你现在正在寻找被允许的 why 的动机。答案非常简单:
因为 child
公开派生自 base
,也称为具有“is-a”关系,任何 child
对象,从字面意义上讲,is a(特殊类型)base
对象。在设计良好的 class 层次结构中,您可以在任何需要 base
对象的地方使用 child
对象(并且 it will honor the interface invariants).这是一个重要的概念,这就是为什么它有一个名字:Liskov 替换原则。(这也是为什么你在布满灰尘的旧书中找到的关于 class 设计的例子有Rectangle
derive from Shape
和 Square
derive from Rectangle
是非常糟糕的例子:在软件设计中,“is a” means/should 与它在软件设计中的含义不同数学。)
现在,您可以从 base
对象移动构造一个 base
对象,是吗?由于 child
对象 是 一个 base
对象,因此您也可以从那个 child
移动构造一个 base
。 (当然,除非你违反通过从 base
公开派生而建立的契约。如果你愿意,C++ 会给你一把机关枪和胶带来擦掉你的脚。)
鉴于此代码:
#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 = '[=12=]';
}
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 = '[=12=]';
}
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
的(移动)构造发生在同一堆栈帧的不同区域,并复制从 base
派生的 child
的部分。从现在开始,child
剩下的就是 base
部分了,只剩下 base
.
我知道堆栈上的对象不可能实现多态性,我想我理解原因:效率。没有 vtable 查找等。编译器可以选择在编译时调用的方法。
我不明白的是:当构成 child
的所有内容时,为什么可以将 child
实例分配给 base
变量(在堆栈框架中)迷路?这样做的动机是什么?我期望编译错误或至少是警告,因为我想不出允许它的充分理由。
请记住 base o = child('a', 'b')
实际上是在调用复制构造函数,因此您并没有真正破坏 child
对象,而是从 [=13= 构造 o
] 作为副本。您需要使用 pointers/references 来对一个基础对象进行多态访问。
child c = child('a', 'b');
base o = c; // at this point we have two objects.
其他语言,例如 Java 和 C# 具有类似的处理对象的语法,但它们实际上使用引用,因此与我们在这里所做的完全不同。
此外,在某些情况下,base 和 subclass 无论如何都具有相同的大小。一个激励案例是 class,它只是 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中输出"Copy"。但是,如果您添加自己的移动构造函数,则会调用它,因为 Der() 是右值。
请记住,移动构造函数是 C++ 的新增功能,因此在 11 之前,该语法是允许的。由于它们必须支持遗留的 C++ 语法,因此它们实际上无法阻止它进行编译,仅仅因为它自从添加了移动构造函数以来并不总是那么有意义。这可能是问题的真正答案:遗留问题,因为在自动生成的移动构造函数中切片更值得商榷。
这对于评论来说太长了,实际上是答案的一部分:
我相信现在很明显您实际上是在创建一个新的 base
对象,而不是将一些现有值分配给变量(因为我们不在 Java 这里;C++获得 Java 行为的代码类似于 std::shared_ptr<base> o = std::make_shared<child>('a','b');
)。另外根据评论,你现在正在寻找被允许的 why 的动机。答案非常简单:
因为 child
公开派生自 base
,也称为具有“is-a”关系,任何 child
对象,从字面意义上讲,is a(特殊类型)base
对象。在设计良好的 class 层次结构中,您可以在任何需要 base
对象的地方使用 child
对象(并且 it will honor the interface invariants).这是一个重要的概念,这就是为什么它有一个名字:Liskov 替换原则。(这也是为什么你在布满灰尘的旧书中找到的关于 class 设计的例子有Rectangle
derive from Shape
和 Square
derive from Rectangle
是非常糟糕的例子:在软件设计中,“is a” means/should 与它在软件设计中的含义不同数学。)
现在,您可以从 base
对象移动构造一个 base
对象,是吗?由于 child
对象 是 一个 base
对象,因此您也可以从那个 child
移动构造一个 base
。 (当然,除非你违反通过从 base
公开派生而建立的契约。如果你愿意,C++ 会给你一把机关枪和胶带来擦掉你的脚。)