是否允许在 Base 实例上写入 Derived 实例?
可以说,代码
class Derived: public Base {....}
Base* b_ptr = new( malloc(sizeof(Derived)) ) Base(1);
b_ptr->f(2);
Derived* d_ptr = new(b_ptr) Derived(3);
b_ptr->g(4);
d_ptr->f(5);
看起来很合理,LSP 也很满意。
我怀疑当 Base 和 Derived 是 POD 时,此代码是标准允许的,否则不允许(因为 vtbl ptr 被覆盖)。我的问题的第一部分是:请指出这种覆盖的确切前提条件。
可能存在其他标准允许的重写方式。
我的问题的第二部分是:还有其他方法吗?他们的具体前提条件是什么?
更新:我不想写这样的代码;我对这样的代码理论上的可能性(或不可能性)感兴趣。所以,这是“标准纳粹”问题,而不是“我怎样才能……”的问题。 (我的问题要转移到其他 stackoverflow 站点吗?)
UPDATE2&4:析构函数怎么样?该代码的假定语义是“基本实例由派生实例的切片(破坏性地)更新”。为了简单起见,让我们假设基类有一个简单的析构函数。
UPDATE3:对我来说最有趣的是通过b_ptr->g(4)访问的有效性
Say, the code
class Derived: public Base {....}
Base* b_ptr = new( malloc(sizeof(Derived)) ) Base(1);
b_ptr->f(2);
Derived* d_ptr = new(b_ptr) Derived(3);
b_ptr->g(4);
d_ptr->f(5);
seems to be reasonable and LSP is satisfied.
I suspect that this code is standard-allowed when Base and Derived are POD, and disallowed otherwise (because vtbl ptr is overwritten). The first part of my question is: please point out precise precondition of such an overwrite.
There may exist other standard-allowed ways to write over.
The second part of my question is: is there any other ways? What are their precise preconditions?
UPDATE: I do NOT want to write code like this; I am interested in theoretical possiblity (or impossibiliy) of such a code. So, this is "standard nazi" question, not a question "how can I ...". (Has my question to be moved to other stackoverflow site?)
UPDATE2&4: What about destructors? Supposed semantics of this code is "Base instance is (destructively) updated by slice of Derived instance". Let us assume, for the sake of simplicity, that Base class has a trivial destructor.
UPDATE3: The most intersting for me is the validity of access via b_ptr->g(4)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
您确实需要在
Derived
的放置新之后执行b_ptr = d_ptr
,以防Base
子对象不是布局中的第一个。代码>派生。正如所写,
b_ptr->g(4)
会引发未定义的行为。规则(3.8
basic.life
):您可能还应该在重用旧对象的内存之前销毁旧对象,但标准并未强制执行此操作。然而,如果不这样做将会泄漏旧对象拥有的任何资源。标准第 3.8 节 (
basic.life
) 给出了完整规则:You really need to do
b_ptr = d_ptr
after placement-new ofDerived
, in case theBase
subobject isn't first in the layout ofDerived
. As written,b_ptr->g(4)
evokes undefined behavior.The rule (3.8
basic.life
):You also should probably be destroying the old object before reusing its memory, but the standard doesn't mandate this. However failure to do so will leak any resources owned by the old object. The full rule is given in section 3.8 (
basic.life
) of the Standard:您正在初始化同一块内存两次。那不会有好的结局。
例如,假设
Base
构造函数分配一些内存并将其存储在指针中。第二次通过构造函数时,第一个指针将被覆盖,内存泄漏。You are initializing the same piece of memory twice. That won't end well.
Suppose for example that the
Base
constructor allocates some memory and stores it in a pointer. The second time through the constructor, the first pointer will be overwritten and the memory leaked.我认为覆盖是允许的。
如果是我,我可能会在重用存储之前调用
Base::~Base
,以便原始对象的生命周期干净地结束。但该标准明确允许您在不调用析构函数的情况下重用存储。我不相信您通过 b_ptr 的访问是有效的。基础对象的生命周期已结束。
(有关生命周期的规则,请参阅任一标准中的 3.8/4。)
而且我也不完全相信 b_ptr 必须提供与 malloc() 调用最初返回的地址相同的地址。
I think the overwrite is allowed.
If it was me I'd probably call
Base::~Base
before reusing the storage, so that the lifetime of the original object ended cleanly. But the standard explicitly allows you to reuse the storage without calling the destructor.I don't believe your access via b_ptr is valid. The lifetime of the Base object has ended.
(See 3.8/4 in either standard for the rules on lifetime.)
And I'm also not entirely convinced that b_ptr must give the same address as was originally returned by the malloc() call.
如果您更清晰地编写这段代码,就更容易看出出了什么问题:
问题是在标记为 (#1) 的行处,
b
和d
完全指向单独的、不相关的事物,并且由于您覆盖了以前的对象*b
的内存,因此b
实际上根本不再有效。您可能对
Base*
和Derived*
是可转换指针类型有一些误导性的想法,但这与当前情况无关,并且就本示例而言,这两种类型也可能完全无关。当我们执行实际转换时,我们仅在最后两行中使用了Derived*
可转换为Base*
的事实。但请注意,此转换是真正的值转换,并且d
与static_cast ( 不是同一指针
) d)
(至少就语言而言)。最后,让我们清理这个烂摊子:
销毁原始
*b
的机会已经过去了,所以我们可能已经泄露了。If you write this code more cleanly, it is easier to see what goes wrong:
The problem is that at the line marked (#1),
b
andd
point to entirely separate, unrelated things, and since you overwrote the memory of the erstwhile object*b
,b
is in fact no longer valid at all.You may have some misguided thoughts about
Base*
andDerived*
being convertible pointer types, but that has nothing to do with the present situation, and for the sake of this example, the two types may as well be entirely unrelated. It is only one the very last two lines that we use the fact that theDerived*
is convertible to aBase*
, when we perform the actual conversion. But note that this conversion is a genuine value conversion, andd
is not the same pointer asstatic_cast<Base*>(d)
(at least as far as the language is concerned).Finally, let's clean up this mess:
The opportunity to destroy the original
*b
has passed, so we may have leaked that.