切入我错C++的地方

Slicing in C++ where I am wrong?

本文关键字:C++      更新时间:2023-10-16

我在C++中读到了切片问题,并尝试了一些例子(我来自Java背景(。不幸的是,我不理解一些行为。目前,我被困在这个例子中(来自Efficent C++第三版的替代示例(。谁能帮我理解它?

我的家长简单类:

class Parent
{
public:
    Parent(int type) { _type = type; }
    virtual std::string getName() { return "Parent"; }
    int getType() { return _type; }
private:
    int _type;
};

我的简单儿童课:

class Child : public Parent
{
public:
    Child(void) : Parent(2) {};
    virtual std::string getName() { return "Child"; }
    std::string extraString() { return "Child extra string"; }
};

主要:

void printNames(Parent p)
{
    std::cout << "Name: " << p.getName() << std::endl;
    if (p.getType() == 2)
    {
        Child & c = static_cast<Child&>(p);
        std::cout << "Extra: " << c.extraString() << std::endl;
        std::cout << "Name after cast: " << c.getName() << std::endl;
    }
}
int main()
{
    Parent p(1);
    Child c;
    printNames(p);
    printNames(c);
}

在我得到的执行之后:

姓名:家长

姓名:家长

额外

:子项额外字符串

演员后姓名:父级

我理解前两行,因为它是"切片"的原因。但我不明白为什么我可以通过静态强制转换将孩子转换为父母。书中写道,切片后,所有专业信息都会被切掉。所以我想,我不能将 p 转换为 c,因为我没有信息(在函数 printNames 的开头,在没有附加信息的情况下创建了一个新的 Parent 对象(。

此外,如果演员阵容

成功,为什么我会得到"演员后的名字:父母"而不是"演员后的名字:孩子"?

你的结果是一次非凡的厄运。这是我得到的结果:

Name: Parent
Name: Parent
Extra: Child extra string
bash: line 8:  6391 Segmentation fault      (core dumped) ./a.out

下面是代码中发生的情况:

当您将c传递给printNames 时,会发生转换。特别是,由于传递是按值传递的,因此调用 Parent 的复制构造函数,该构造函数是隐式声明的,其代码如下所示:

Parent(Parent const& other) : _type{other._type} {}

换句话说,您复制了 c_type 变量,而不是其他任何内容。您现在有了一个类型为 Parent 的新对象(其静态和动态类型都Parent (,并且c实际上根本没有传递到printNames中。

在函数内部,然后强制将p转换为Child&。这个强制转换不能成功,因为p根本不是一个Child,或者可以转换为一个,但是C++并没有给你任何诊断(这实际上是一种耻辱,因为编译器可以微不足道地证明强制转换是错误的(。

现在

我们处于不确定行为的土地上,现在一切都被允许发生。实际上,由于Child::extraString从不访问this(隐式或显式(,因此对该函数的调用会成功。调用是在非法对象上完成的,但由于该对象从未被触摸过,因此可以工作(但仍然是非法的!

下一个调用,Child::getName ,是一个虚拟调用,因此它需要显式访问this(在大多数实现中,它访问虚拟方法表指针(。再说一次,因为代码无论如何都是UB,所以任何事情都可能发生。您很"幸运",代码刚刚抓住了父类的虚拟方法表指针。使用我的编译器,该访问显然失败了。

那段代码太可怕了。 发生了什么事情:

  • printNames(c)切片c,从嵌入在调用方c对象中的Parent对象复制构造本地p,然后设置p指向Parent虚拟调度表的指针。

  • 因为 Parent 的数据成员是从 c 复制的,所以p的类型为 2,并且输入了if分支

  • Child & c = static_cast<Child&>(p);有效地告诉编译器"相信我(我是一名程序员(,我知道p实际上是一个我想引用的Child对象",但这是一个公然的谎言,因为p实际上是从Child c复制的Parent对象

    • 作为程序员,如果您不确定它是否有效,则有责任不要求编译器执行此操作
  • 编译器静态(在编译时(找到c.extraString(),因为它知道cChild(或进一步派生的类型,但c.extraString不是virtual,因此可以静态解析;在Parent对象上执行此操作是未定义的行为,但可能是因为extraString不会尝试访问只有Child对象才会具有的任何数据, 它表面上为您运行"正常">

  • c.getName()virtual的,所以编译器使用对象的虚拟调度表 - 因为对象实际上是一个Parent它动态解析(在运行时(到Parent::getName函数并产生关联的输出

    • 虚拟调度的实现是定义的实现,您的未定义行为可能不会在所有C++实现中以这种方式运行,甚至在所有优化级别,使用所有编译器选项等。
相关文章:
  • 没有找到相关文章