为什么是“对象切片”? C++ 中需要?为什么是允许的?为了更多的错误?
为什么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 是多态的,而 1 是臭名昭著的对象 切片只会产生一个错误!
注1、2和3需要是-与工作的关系。
如果您使用私有/保护继承,您将得到所有这些的编译错误:
'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 ++标准会更好吗 让 1 成为编译错误,同时继续允许 2 和 3 ?
希望这次我能更好地表达我的问题。
谢谢
Why C++ standard allow object slice ?
Please don't explain c++ object slice concept to me as I knew that.
I am just wondering what's the intention behind this c++ feature(object slice) design ?
To get novice more bugs?
Wouldn't it be more type safe for c++ to prevent object slice ?
Below is just a standard and basic slice example:
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
}
Thanks in advance.
=================================================================================
After reading all the answers and comments I think I should express my question better in the first place but here it comes:
When there is a is-a relationship(public inheritnace), instead of private/protected inheritance , you can do the following:
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
}
It's well known that only 2 & 3 works polymorphically while one is the infamous object
slicing which produce nothing but a bug !
Note 1, 2 and 3 NEED is-a relationship to work.
If you are using private/protect inheritance, you will get compile error for all of them :
'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
So my question(original intention) was to ask would it be better if c++ standard
make 1 a compile error while keep allowing 2 and 3 ?
Hope I have expressed my question better this time.
Thanks
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
我认为你正在向后看。
没有人坐下来说“好吧,我们需要用这种语言进行切片。”切片本身并不是一种语言功能;而是一种语言功能。这是当您打算多态地使用对象但却出错并复制它们时所发生的情况的名称。你可能会说这是一个程序员错误的名字。
可以“静态”复制对象是 C++ 和 C 的基本功能,否则您将无法做很多事情。
编辑:[杰里·科芬(Jerry Coffin)(希望托马拉克会原谅我劫持他的答案)]。我添加的大部分内容都遵循相同的思路,但更直接地来自源代码。一个例外(正如您将看到的)是,奇怪的是,有人确实说“我们需要用这种语言进行切片”。 Bjarne 在《The Design and Evolution of C++》(第 11.4.4 节)中谈论了一些关于切片的内容。他说:
我注意到 Ravi Sethi 是《龙》一书的作者之一(以及其他许多著作),所以无论你是否同意他的观点,我认为很容易理解他对语言设计的看法会带来相当多的影响。重量。
I think you're looking at it backwards.
Nobody sat down and said "OK, we need slicing in this language." Slicing in itself isn't a language feature; it's the name of what happens when you meant to use objects polymorphically but instead went wrong and copied them. You might say that it's the name of a programmer bug.
That objects can be copied "statically" is a fundamental feature of C++ and C, and you wouldn't be able to do much otherwise.
Edit: [by Jerry Coffin (hopefully Tomalak will forgive my hijacking his answer a bit)]. Most of what I'm adding is along the same lines, but a bit more directly from the source. The one exception (as you'll see) is that, strangely enough, somebody did actually say "we need slicing in this language." Bjarne talks a bit about slicing in The Design and Evolution of C++ (§11.4.4). Among other things he says:
I'd note that Ravi Sethi is one of the authors of the dragon book (among many other things), so regardless of whether you agree with him, I think it's easy to understand where his opinion about language design would carry a fair amount of weight.
这是允许的,因为
is-a
关系。当您公开1从
Base
派生Derived
时,您就是在向编译器宣布Derived< /code> 是一个
基础
。因此,应该允许这样做:然后按原样使用
base
。即:阅读以下内容:
1。如果您私下从
Base
派生Derived
,那么它就是has-a
关系。这就是一种构图。阅读此和这个。但是,就您的情况而言,如果您不想切片,那么您可以这样做:
根据您的评论:
不会。如果基类具有虚函数,则指针和引用不会维持
is-a
关系。当您希望一种类型的一个对象表现得像其基本类型的对象一样时,这称为
is-a
关系。如果你使用基类型的指针或引用,那么它不会调用Base::message()
,这表明,指针或引用不具有像<的对象的指针或引用em>基本类型。It's allowed because of
is-a
relationship.When you publicly1 derive
Derived
fromBase
, you're annoucing to the compiler thatDerived
is aBase
. Hence it should be allowed to do this:and then use
base
as it is. that is:Read this:
1. If you privately derive
Derived
fromBase
, then it ishas-a
relationship. That is sort of composition. Read this and this.However, in your case, if you don't want slicing, then you can do this:
From your comment :
No. Pointer and Reference doesn't maintain
is-a
relationship if the base has virtual function(s).When you want one object of one type to behave like an object of it's base type, then that is called
is-a
relationship. If you use pointer or reference of base type, then it will not call theBase::message()
, which indicates, pointer or reference doesn't have like a pointer or reference to an object of base type.您将如何防止语言中的对象切片?如果一个函数期望堆栈上有 16 个字节(例如作为参数),并且您传递了一个更大的对象,即 24 个字节,那么被调用者到底如何知道该怎么做? C++ 与 Java 不同,Java 中的所有内容都是底层的引用。简而言之,假设 C++ 与 C 一样,允许对象使用值和引用语义,则无法避免对象切片。
编辑:有时您并不关心对象是否切片,并且完全禁止它可能会阻止有用的功能。
How would you prevent object slicing within the language? If a function is expecting 16 bytes on the stack (as a parameter for example) and you pass a bigger object that's say 24 bytes how on Earth would the callee know what to do? C++ isn't like Java where everything is a reference under the hood. The short answer is that there's just no way to avoid object slicing assuming that C++, like C, allows value and reference semantics for objects.
EDIT: Sometimes you don't care if the object slices and prohibiting it outright would possibly prevent a useful feature.
对象切片是继承和可替代性的自然结果,它不仅限于C++,也不是刻意引入的。接受 Base 的方法只能看到 Base 中存在的变量。复制构造函数和赋值运算符也是如此。然而,他们通过复制这个可能有效也可能无效的切片对象来传播问题。
当您将多态对象视为值类型时,最常出现这种情况,其中涉及过程中的复制构造函数或赋值运算符,这些操作符通常是编译器生成的。当您使用多态对象时,请始终使用引用或指针(或指针包装器),切勿在游戏中混合值语义。如果您想要多态对象的副本,请改用动态
clone
方法。一种解决方案是在分配时检查源对象和目标对象的
typeid
,如果不匹配则抛出异常。不幸的是,这不适用于复制构造函数,您无法分辨正在构造的对象的类型,即使对于 Derived,它也会报告 Base。另一种解决方案是通过从 boost::noncopyable 私有继承或将复制构造函数和赋值运算符设为私有来禁止复制和赋值。前者也不允许编译器生成的复制构造函数和赋值运算符在所有子类中工作,但您可以在子类中定义自定义的复制构造函数和赋值运算符。
另一种解决方案是对复制构造函数和赋值运算符进行保护。这样,您仍然可以使用它们来简化子类的复制,但局外人不会意外地以这种方式切片对象。
就我个人而言,我私下从我自己的
NonCopyable
导出,其中和Boost几乎一样。此外,在声明值类型时,我公开并虚拟地从Final派生它们。
(和 ValueType)以防止任何类型的多态性。但仅在调试模式下,因为它们增加了对象的大小,并且程序的静态结构在发布模式下无论如何都不会改变。我必须重复一遍:对象切片可能发生在您读取 Base 变量并对它们执行某些操作的任何地方,请确保您的代码不会传播它或在发生这种情况时行为不正确。
Object slicing is a natural consequence of inheritance and substitutability, it is not limited to C++, and it was not introduced deliberately. Methods accepting a Base only see the variables present in Base. So do copy constructors and assignment operators. However they propagate the problem by making copies of this sliced object that may or may not be valid.
This most often arises when you treat polymorphic objects as value types, involving the copy constructor or the assignment operator in the process, which are often compiler generated. Always use references or pointers (or pointer wrappers) when you work with polymorphic objects, never mix value semantics in the game. If you want copies of polymorphic objects, use a dynamic
clone
method instead.One half-solution is to check the
typeid
of both the source and the destination objects when assigning, throwing an exception if they do not match. Unfortunately this is not applicable to copy constructors, you can not tell the type of the object being constructed, it will report Base even for Derived.Another solution is to disallow copying and assigning, by inheriting privately from
boost::noncopyable
or making the copy constructor and assignment operator private. The former disallows the compiler generated copy constructor and assignment operator from working in all subclasses as well, but you can define custom ones in subclasses.Yet another solution is to make the copy constructor and assignment operator protected. This way you can still use them to ease the copying of subclasses, but an outsider can not accidentally slice an object this way.
Personally I derive privately from my own
NonCopyable
, which is almost the same as the Boost one. Also, when declaring value types, I publicly and virtually derive them fromFinal<T>
(and ValueType) to prevent any kind of polymorphism. Only in DEBUG mode though, since they increase the size of objects, and the static structure of the program doesn't change anyway in release mode.And I must repeat: object slicing can occur anywhere where you read the variables of Base and do something with them, be sure your code does not propagate it or behave incorrectly when this occurs.
基础对象对
m_base
到底有什么访问权限?你不能这样做
baseObj.m_base = x;
它是一个私有成员。您只能使用基类中的公共方法,因此这与仅创建基对象没有太大区别。Exactly what access does the base object have to
m_base
?You can't do
baseObj.m_base = x;
It is a private member. You can only use public methods from the base class, so it is not much different to just creating a base object.