【问题标题】:Why is 'object slice' needed in C++ ? Why it is allowed ? For more bugs?为什么 C++ 中需要“对象切片”?为什么允许?更多错误?
【发布时间】:2011-09-22 14:32:40
【问题描述】:

为什么 C++ 标准允许对象切片?

请不要向我解释 c++ 对象切片的概念,因为我知道。

我只是想知道这个 c++ 特性(对象切片)设计背后的意图是什么?

给新手更多的bug?

c++ 防止对象切片不是更安全吗?

以下只是一个标准和基本的切片示例:

class Base{
public:
       virtual void message()
       {
               MSG("Base ");
       }
private:
    int m_base;
};

class Derived : public Base{
public:
       void message()
       {
               MSG("Derived "); 
       }
private:
       int m_derive;
};

int main (void)
{
    Derived dObj;

    //dObj get the WELL KNOWN c++ slicing below
    //evilDerivedOjb is just a Base object that cannot access m_derive
    Base evilDerivedOjb = dObj;  //evilDerivedObj is type Base
    evilDerivedOjb.message();    //print "Baes" here of course just as c++ standard says
}

提前致谢。

================================================ =================================== 在阅读了所有答案和 cmets 之后,我认为我应该首先更好地表达我的问题,但它来了:

当存在 is-a 关系(公共继承),而不是私有/受保护继承时,您可以执行以下操作:

class Base{
public:
    virtual void foo(){MSG("Base::foo");}
};

class Derived : public Base{
public:
    virtual void foo(){MSG("Derived::foo");}
};

int main (void)
{
    Base b;
    Derived d;
    b = d;                      //1
    Base * pB = new Derived();  //2
    Base& rB = d;               //3

    b.foo();    //Base::foo
    pB->foo();  //Derived::foo
    rB.foo();   //Derived::foo
}

众所周知,只有 2 和 3 是多态的,而一个是臭名昭著的对象 切片只会产生错误!

注意 1、2 和 3 NEED 是与工作的关系。
如果你使用私有/保护继承,你会得到所有的编译错误:

'type cast' : conversion from 'Derived *' to 'const Base &' exists, but is inaccessible
'type cast' : conversion from 'Derived *' to 'Base *' exists, but is inaccessible
'type cast' : conversion from 'Derived *' to 'Base &' exists, but is inaccessible

所以我的问题(初衷)是问如果c++标准会更好吗 在允许 2 和 3 的同时使 1 成为编译错误?

希望这次我能更好地表达我的问题。

谢谢

【问题讨论】:

  • 不是。 Closevote 是错误的。
  • 我猜投票是因为faq:“没有实际问题需要解决”。或者“伪装成问题的咆哮”。
  • 咆哮伪装成一个问题...
  • @Gob00st:从好的方面来说,看到有人理解切片令人耳目一新。 :)
  • 虽然措辞肯定更圆滑,但我认为这是一个足够公平的问题。投票重新开放。

标签: c++ standards slice


【解决方案1】:

我认为你是在向后看。

没有人坐下来说“好的,我们需要用这种语言进行切片。”切片本身不是语言功能。它是当您打算以多态方式使用对象但出错并复制它们时发生的事情的名称。你可能会说它是程序员 bug 的名字。

可以“静态”复制对象是 C++ 和 C 的基本特征,否则您将无能为力。

编辑:[由 Jerry Coffin 撰写(希望 Tomalak 能原谅我对他的回答有点劫持)]。我添加的大部分内容都是相同的,但更直接来自源代码。一个例外(如您所见)是,奇怪的是,有人确实实际上说“我们需要用这种语言进行切片”。 Bjarne 在The Design and Evolution of C++ (§11.4.4) 中谈到了切片。他说:

从实际的角度来看,我对切片持怀疑态度,但除了添加一个非常特殊的规则外,我看不出有任何防止它的方法。此外,当时,我从 Ravi Sethi 那里获得了对这些“切片语义”的独立请求,他从理论和教学的角度想要它:除非您可以将派生类的对象分配给其公共基础的对象类,那么这将是 C++ 中唯一不能使用派生对象代替基对象的点。

我注意到 Ravi Sethi 是龙书的作者之一(除其他外),所以不管你是否同意他的观点,我认为很容易理解他对语言设计的看法会带来什么相当的重量。

【讨论】:

    【解决方案2】:

    因为is-a关系所以允许。

    当您公开1Base 派生Derived 时,您向编译器宣布Derived 是一个 Base。因此应该允许这样做:

    Base base = derived;
    

    然后按原样使用base。那就是:

    base.message(); //calls Base::message();
    

    阅读:

    1.如果您私下Base 派生Derived,那么它就是has-a 关系。那是一种组合。阅读thisthis

    但是,在你的情况下,如果你不想切片,那么你可以这样做:

    Base & base = derived;
    base.message(); //calls Derived::message();
    

    来自您的评论:

    C++ 防止对象切片,而只允许指针/引用为 is-a 关系工作不是更好吗???

    没有。如果基础具有虚函数,则指针和引用不保持 is-a 关系。

     Base *pBase = &derived;
     pBase->message(); //doesn't call Base::message(). 
     //that indicates, pBase is not a pointer to object of Base type.
    

    当您希望一种类型的对象表现得像它的基本类型的对象时,这称为is-a 关系。如果使用基类型的指针或引用,则不会调用Base::message(),这表明,指针或引用不像类型的对象的指针或引用。

    【讨论】:

    • “is-a”关系仅表示“公共继承”,则可用于c++多态。但是多态性只适用于指针或引用,而不是对象!
    • C++ 防止对象切片,而只允许指针/引用为 is-a 关系工作不是更好吗???
    • @Nawaz,我明白你的意思。但是像“Base base = derived;”这样的对象切片代码有什么实际用途吗? ?
    • @Gob00st:可以。您基本上是在问:is-a 关系有什么实际用途吗?我会说,肯定是的。否则,为什么所有面向对象的语言都会谈论这个?这是面向对象的固有特征。
    • @Nawaz,是的,可能有......但似乎每当我看到这个,是对象切片的一个坏例子,这就是我提出这个问题的原因。
    【解决方案3】:

    您将如何防止语言中的对象切片?如果一个函数期望堆栈上有 16 个字节(例如作为参数)并且您传递了一个更大的对象,即 24 个字节,那么被调用者究竟如何知道该怎么做? C++ 不像 Java,所有东西都是底层的引用。简短的回答是,假设 C++ 像 C 一样允许对象的值和引用语义,就没有办法避免对象切片。

    编辑:有时您不在乎对象切片并完全禁止它可能会阻止有用的功能。

    【讨论】:

      【解决方案4】:

      对象切片是继承和可替换性的自然结果,并不局限于C++,也不是特意引入的。接受 Base 的方法只能看到 Base 中存在的变量。复制构造函数和赋值运算符也是如此。然而,他们通过复制这个可能有效也可能无效的切片对象来传播问题。

      当您将多态对象视为值类型时,通常会出现这种情况,涉及过程中的复制构造函数或赋值运算符,它们通常是编译器生成的。当您使用多态对象时,请始终使用引用或指针(或指针包装器),切勿在游戏中混合值语义。如果您想要多态对象的副本,请改用动态clone 方法。

      一个半解决方案是在分配时检查源对象和目标对象的typeid,如果它们不匹配则抛出异常。不幸的是,这不适用于复制构造函数,您无法分辨正在构造的对象的类型,即使是 Derived 也会报告 Base。

      另一个解决方案是禁止复制和分配,方法是从boost::noncopyable 私有继承或将复制构造函数和赋值运算符设为私有。前者也不允许编译器生成的复制构造函数和赋值运算符在所有子类中工作,但您可以在子类中定义自定义。

      另一个解决方案是使复制构造函数和赋值运算符受保护。这样你仍然可以使用它们来简化子类的复制,但外人不会意外地以这种方式切片对象。

      我个人是从我自己的NonCopyable 私下派生出来的,这与 Boost 几乎相同。此外,在声明值类型时,我公开并虚拟地从 Final<T>(和 ValueType)派生它们,以防止任何类型的多态性。但仅在调试模式下,因为它们会增加对象的大小,并且程序的静态结构在发布模式下无论如何都不会改变。

      我必须重复一遍:对象切片可能发生在您读取 Base 的变量并对其进行处理的任何地方,请确保您的代码不会传播它或在发生这种情况时行为不正确。

      【讨论】:

        【解决方案5】:

        基础对象对m_base的确切访问权限是什么?

        你不能baseObj.m_base = x;它是私人会员。您只能使用基类中的公共方法,因此与仅创建基对象没有太大区别。

        【讨论】:

        • 问题已被编辑。当我写它时,我的回答是适当的。该问题最初是关于安全问题,因为基础对象可以访问基础数据。
        猜你喜欢
        • 2020-08-29
        • 2015-07-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-03-17
        • 2016-11-22
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多