为什么C++需要'object slice'?为什么允许?更多错误?

Why is 'object slice' needed in C++ ? Why it is allowed ? For more bugs?

本文关键字:为什么 错误 object C++ 需要 slice      更新时间:2023-10-16

为什么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
}

提前感谢。

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

如果存在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多态工作,而一个是臭名昭著的对象切片,只产生一个bug !

注释1、2和3 需要是一个工作关系
如果你使用private/protect继承,你会得到编译错误:

'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出现编译错误?

希望我这次把问题表达得更好。

谢谢

我想你看反了。

没有人坐下来说:"好吧,我们需要在这门语言中使用切片。"切片本身并不是一种语言特性;这是当你打算多态地使用对象,但却出错并复制它们时发生的事情的名称。你可能会说这是一个程序员错误的名字。

对象可以"静态"复制是c++和C的一个基本特性,否则你就做不了什么了。

编辑:[作者:Jerry Coffin(希望Tomalak能原谅我稍微劫持了他的答案)]。我添加的大部分内容都是沿着相同的路线,但更直接地来自源代码。一个例外(正如您将看到的)是,非常奇怪的是,有人确实说"我们需要这种语言中的切片"。Bjarne在 c++ 的设计和演变(§11.4.4)中谈到了切片。他说:

从实用的角度来看,我对切片持怀疑态度,但我认为除了添加一个非常特殊的规则之外,没有任何方法可以防止它。而且,当时我从Ravi Sethi那里得到了一个独立的"切片语义"的请求,他从理论和教学的角度想要它:除非你可以将派生类的对象赋值给它的公共基类的对象,否则这将是c++中唯一不能使用派生对象代替基对象的地方。

我要指出Ravi Sethi是《龙之书》的作者之一,所以不管你是否同意他的观点,我认为很容易理解他关于语言设计的观点会有相当大的分量。

这是允许的,因为is-a关系。

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

Base base = derived;

,然后按原样使用base。即:

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

:

    <
  • 是一个关系/gh>

<一口> 1。如果私地Base导出Derived,则是has-a关系。这是一种组合。读读这个和这个。

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

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(),这表明,指针或引用不像指针或引用指向类型的对象。

您如何在语言中防止对象切片?如果一个函数期望在堆栈上有16个字节(例如作为参数),而你传递了一个更大的对象,比如24个字节,被调用者怎么知道该怎么做?c++不像Java,所有东西都是引擎盖下的引用。简单的回答是,如果c++像C一样,允许对象的值和引用语义,那么就没有办法避免对象切片。

编辑:有时你不关心对象是否切片,直接禁止它可能会阻止一个有用的功能。

对象切片是继承和可替代性的自然结果,它不仅限于c++,而且不是故意引入的。接受Base的方法只能看到Base中存在的变量。复制构造函数和赋值操作符也是如此。然而,它们通过复制这个切片对象的副本来传播这个问题,这些副本可能有效,也可能无效。

当您将多态对象视为值类型时,通常会出现这种情况,涉及过程中的复制构造函数或赋值操作符,这些操作符通常是编译器生成的。当你使用多态对象时,总是使用引用或指针(或指针包装器),永远不要在游戏中混合值语义。如果需要多态对象的副本,请使用动态clone方法。

一个半解决方案是在赋值时同时检查源对象和目标对象的typeid,如果它们不匹配则抛出异常。不幸的是,这不适用于复制构造函数,你不能告诉正在构造的对象的类型,它会报告Base甚至派生。

另一个解决方案是禁止复制和赋值,通过私有继承boost::noncopyable或将复制构造函数和赋值操作符设置为私有。前者禁止编译器生成的复制构造函数和赋值操作符在所有子类中工作,但可以在子类中定义自定义操作符。

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

就我个人而言,我私下从我自己的NonCopyable中获得,这几乎与Boost相同。另外,在声明值类型时,我公开地、虚拟地从Final<T>(和ValueType)派生它们,以防止任何类型的多态性。只有在DEBUG模式下,因为它们增加了对象的大小,并且程序的静态结构在发布模式下不会改变。

我必须重复:对象切片可以发生在任何地方,你读取Base的变量并对它们做一些事情,确保你的代码不会传播它或当这种情况发生时行为不正确。

基本对象对m_base有什么访问权限?

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

相关文章: