【问题标题】:C++ slicing caused by reference引用引起的 C++ 切片
【发布时间】:2013-06-14 18:06:15
【问题描述】:

我无法理解以下代码的行为,我根据 C++ 切片的示例对其进行了修改:

#include <stdio.h>
#include <iostream>

struct B { 
    int x; 
    B() { x = 0; } 
    virtual void foo( const char* id ) { 
        std::cout << id << ": B="  << this << ", x=" << x << std::endl; 
    } 
};

struct D1 : B  { 
    int y;
    D1() { x = 1; y = 100; } 
    virtual void foo( const char* id ) { 
        std::cout << id << ": D1=" << this << ", x=" << x << ", y=" << y << std::endl; 
    } 
} d1;

struct D2 : B  {
    int z;
    D2() { x = 2; z = 200; } 
    virtual void foo( const char* id ) { 
        std::cout << id << ": D2=" << this << ", x=" << x << ", z=" << z  << std::endl; 
    } 
} d2;

void main() {
    std::cout << "d1 = " << &d1 << std::endl;
    std::cout << "d2 = " << &d2 << std::endl;

    std::cout << "By pointer at beginning: " << std::endl;
    B* pb = &d1;
    pb->foo( "pd1" );
    pb = &d2;
    pb->foo( "pd2" );

    std::cout << "By Value: " << std::endl;
    B b = d1;
    b.foo( "d1" );
    b = d2;
    b.foo( "d2" );

    std::cout << "By pointer after by value: " << std::endl;
    pb = &d1;
    pb->foo( "pd1" );
    pb = &d2;
    pb->foo( "pd2" );

    std::cout << "By reference: " << std::endl;
    B& rb = d1;
    rb.foo( "rd1" );
    rb = d2;
    rb.foo( "rd2" );

    std::cout << "By pointer after by reference: " << std::endl;
    pb = &d1;
    pb->foo( "pd1" );
    pb = &d2;
    pb->foo( "pd2" );
}

//The result is the following:
d1 = 0115B504
d2 = 0115B510
By pointer at beginning:
pd1: D1=0115B504, x=1, y=100
pd2: D2=0115B510, x=2, z=200
By Value:
d1: B=0036FE44, x=1
d2: B=0036FE44, x=2
By pointer after by value:
pd1: D1=0115B504, x=1, y=100
pd2: D2=0115B510, x=2, z=200
By reference:
rd1: D1=0115B504, x=1, y=100
rd2: D1=0115B504, x=2, y=100
By pointer after by reference:
pd1: D1=0115B504, x=2, y=100
pd2: D2=0115B510, x=2, z=200

从上面的结果我们可以看出:

  • 通过删除派生的特定成员,值分配导致分配目标 (b) 的切片问题,但保留分配源(d1 和 d2)不变。
  • 引用分配通过不分配派生的特定成员而导致对分配目标 (rd) 进行切片,从而部分更改分配源(d1 和 d2)。

起初,我很惊讶您可以通过它们的基分配对不同类型(D2 到 D1)的引用,直到我意识到臭名昭著的 C++ 强制转换系统。一个结论似乎是引用只能被初始化而不能被赋值。

我们知道基础对象的 STL 容器存在切片问题,因为除了列表之外,所有其他 STL 容器都复制对象。看起来基本引用的 STL 容器应该不会更好,除非它在里面保存指针。

你们是怎么解决这个问题的?

谢谢! CP

【问题讨论】:

  • "一个结论好像引用只能初始化不能赋值。" - 是的,这是真的。引用是对原始对象的别名
  • “起初,我很惊讶你可以通过它们的基分配对不同类型(D2 到 D1)的引用,直到我意识到臭名昭著的 C++ 转换系统。”你不能。那不是发生的事情。您将 d2 分配给 b 引用的对象。如果您使用调试器完成了此操作,您更有可能看到副本并了解发生了什么。
  • "直到我意识到臭名昭著的 C++ 铸造系统" 不。这里没有演员表。

标签: c++ stl pass-by-reference


【解决方案1】:

我很难在您的文字中找到一个问题,但我会假设它是这样的:

“如何在标准容器中多态存储对象?”

首先,我只想说您不能将引用存储在标准容器中,因为您不能分配(重新绑定)它们。

所以通常的方法是拥有一个基类指针的容器。如果容器拥有这些项目,则使用像unique_ptrshared_ptr 这样的智能指针。如果容器不拥有对象,则只需使用原始指针。

【讨论】:

  • 我认为在 C+11 中你可以找到在容器中存储引用的方法。
【解决方案2】:
B& rb = d1;
rb.foo( "rd1" );
rb = d2;

最后一行和你写的一样

d1 = d2;

除了它仅适用于基类子对象。在第一行中,您将引用创建rb 设置为具有不同静态类型的d1 的别名。无法重新安装引用。后面的分配以对象为目标。 d1 只能通过使用 op= 的诡计来直接分配,但是您更改的静态类型会使 Base::op= 工作。

很多导师说你应该只使用抽象类作为基础,大部分推理指向这样的意外。

在层次结构中,您很少留下 op=,如果绝对需要,您可以使用一些虚拟符号来验证兼容性。

【讨论】:

  • 这里的问题是Base::operator= 不是虚拟的。如果它是虚拟的,那么它将调用派生类中的赋值运算符。
  • @newacct 使Base::operator= 虚拟化本身是不够的。您还需要在派生类中覆盖它。然后,你会在Derived1::operator= 中使用Derived2 做什么?
  • 我有一个支持赋值的层次结构,我使用了一个名为 AssignVirt(const Base&) 的函数来避免混淆。它是由通用宏以及 Clone、Create 等实现的。使用的实现确保 rhs 与 *this 的类型相同,并应用了 stock op=。它允许许多常见的多态代码,而每个客户端通常只处理一个具体类型。
  • @BalogPal 如果 rhs 的类型与 lhs 的类型不匹配怎么办?
  • 在我的情况下,有一个断言并且没有操作,因为我认为正确匹配是一个先决条件。对于其他情况,抛出异常似乎是公平的,或者什么也不做。
猜你喜欢
  • 1970-01-01
  • 2017-03-17
  • 2012-03-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-10-25
  • 2017-01-11
  • 2021-12-31
相关资源
最近更新 更多