为什么C++需要'object slice'?为什么允许?更多错误?
Why is 'object slice' needed in C++ ? Why it is allowed ? For more bugs?
为什么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
关系。
当你公开1从Base
派生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;
,它是一个私有成员。您只能使用基类中的公共方法,因此与仅创建基类对象没有太大区别。
- 我的字符计数代码计算错误.为什么
- 为什么在运行时没有向我们提供有关分段错误的更多信息?
- xmake总是报告:错误:无法获取cxx的程序,为什么
- 为什么与常规GCC不同,即使有"学究性错误",MinGW-GCC也能容忍丢失的返回类型
- 在没有定义返回类型的函数中返回布尔值,并将结果保存在无错误的char编译中-为什么
- 为什么加载SDF会导致Mobilizer创建闭环错误
- 为什么我在leetcode上收到AddressSanitizer:地址0x602000000058上的堆缓冲区溢出错误
- 为什么包含windows.h会产生语法错误,从而阻止类的实例化?(C2146,C2065)
- 为什么我在使用void函数时得到错误代码C2276
- 为什么类中的ostringstream类型的成员会导致";调用隐含删除复制构造函数";错误
- 为什么g++在未执行的代码处标记强制转换错误
- 有人知道为什么在开关中使用stoi函数会返回恒定的错误吗
- 为什么创建友元类的实例会导致"undefined reference to"错误?
- 为什么错误"permission denied","id returned 1 exit status"仅在 IM 使用 C++ 中的头文件 fstream 时才出现
- 为什么错误:"locate_zone"不是"std::chrono"的成员
- 为什么错误 C6386 缓冲区溢出与 strsafe.h 字符串 Cch 函数
- 为什么错误1400无效的窗口句柄
- 为什么错误LNK2001:在这种情况下未解析的外部符号
- c++为什么错误:no match for 'operator='
- 为什么错误"invalid conversion from 'char' to 'const char*"?