是否允许在 Base 实例上写入 Derived 实例?

发布于 2025-01-02 06:56:33 字数 704 浏览 2 评论 0原文

可以说,代码

    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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

最初的梦 2025-01-09 06:56:33

您确实需要在 Derived 的放置新之后执行 b_ptr = d_ptr ,以防 Base 子对象不是 布局中的第一个。代码>派生。正如所写,b_ptr->g(4) 会引发未定义的行为。

规则(3.8 basic.life):

如果在一个对象的生命周期结束后,在该对象所占用的存储空间被重用或释放之前,在原对象所占用的存储位置上创建了一个新对象,则指向该对象的指针原始对象,引用原始对象的引用,或者原始对象的名称将自动引用新对象,并且一旦新对象的生命周期开始,可以
用于操作新对象,如果

  • 新对象的存储位置恰好覆盖原始对象占用的存储位置,
  • 新对象与原始对象具有相同的类型(忽略顶级 cv 限定符),并且
  • 原始对象的类型不是 const 限定的,并且如果是类类型,则不包含任何非静态
    类型为 const 限定的数据成员或引用类型,并且
  • 原始对象是 T 类型的最派生对象 (1.8),新对象是 T 类型的最派生对象(即,它们是 < strong>不是基类子对象)。

您可能还应该在重用旧对象的内存之前销毁旧对象,但标准并未强制执行此操作。然而,如果不这样做将会泄漏旧对象拥有的任何资源。标准第 3.8 节 (basic.life) 给出了完整规则:

程序可以通过重用对象占用的存储空间或通过使用非平凡析构函数显式调用类类型对象的析构函数来结束任何对象的生命周期。对于具有非平凡析构函数的类类型的对象,程序不需要在重用或释放该对象占用的存储空间之前显式调用析构函数;但是,如果没有显式调用
析构函数或如果不使用删除表达式 (5.3.5) 来释放存储,则不得隐式调用析构函数,并且任何依赖于析构函数产生的副作用的程序都具有未定义的行为.

You really need to do b_ptr = d_ptr after placement-new of Derived, in case the Base subobject isn't first in the layout of Derived. As written, b_ptr->g(4) evokes undefined behavior.

The rule (3.8 basic.life):

If, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, a new object is created at the storage location which the original object occupied, a pointer that pointed to the original object, a reference that referred to the original object, or the name of the original object will automatically refer to the new object and, once the lifetime of the new object has started, can
be used to manipulate the new object, if
:

  • the storage for the new object exactly overlays the storage location which the original object occupied,
    and
  • the new object is of the same type as the original object (ignoring the top-level cv-qualifiers), and
  • the type of the original object is not const-qualified, and, if a class type, does not contain any non-static
    data member whose type is const-qualified or a reference type, and
  • the original object was a most derived object (1.8) of type T and the new object is a most derived object of type T (that is, they are not base class subobjects).

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:

A program may end the lifetime of any object by reusing the storage which the object occupies or by explicitly calling the destructor for an object of a class type with a non-trivial destructor. For an object of a class type with a non-trivial destructor, the program is not required to call the destructor explicitly before the storage which the object occupies is reused or released; however, if there is no explicit call to
the destructor or if a delete-expression (5.3.5) is not used to release the storage, the destructor shall not be implicitly called and any program that depends on the side effects produced by the destructor has undefined behavior.

疑心病 2025-01-09 06:56:33

您正在初始化同一块内存两次。那不会有好的结局。

例如,假设 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.

摘星┃星的人 2025-01-09 06:56:33

我认为覆盖是允许的。

如果是我,我可能会在重用存储之前调用 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.

帅哥哥的热头脑 2025-01-09 06:56:33

如果您更清晰地编写这段代码,就更容易看出出了什么问题:

void * addr = std::malloc(LARGE_NUMBER);

Base * b = new (addr) Base;
b->foo();                    // no problem

Derived * d = new (addr) Derived;
d->bar();                    // also fine  (#1)

b->foo();                    // Error! b no longer points to a Base!

static_cast<Base*>(d)->foo(); // OK
b = d; b->foo();              // also OK

问题是在标记为 (#1) 的行处,bd 完全指向单独的、不相关的事物,并且由于您覆盖了以前的对象 *b 的内存,因此 b 实际上根本不再有效。

您可能对 Base*Derived* 是可转换指针类型有一些误导性的想法,但这与当前情况无关,并且就本示例而言,这两种类型也可能完全无关。当我们执行实际转换时,我们仅在最后两行中使用了 Derived* 可转换为 Base* 的事实。但请注意,此转换是真正的转换,并且d static_cast( 不是同一指针) d)(至少就语言而言)。

最后,让我们清理这个烂摊子:

d->~Derived();
std::free(addr);

销毁原始 *b 的机会已经过去了,所以我们可能已经泄露了。

If you write this code more cleanly, it is easier to see what goes wrong:

void * addr = std::malloc(LARGE_NUMBER);

Base * b = new (addr) Base;
b->foo();                    // no problem

Derived * d = new (addr) Derived;
d->bar();                    // also fine  (#1)

b->foo();                    // Error! b no longer points to a Base!

static_cast<Base*>(d)->foo(); // OK
b = d; b->foo();              // also OK

The problem is that at the line marked (#1), b and d 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* and Derived* 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 the Derived* is convertible to a Base*, when we perform the actual conversion. But note that this conversion is a genuine value conversion, and d is not the same pointer as static_cast<Base*>(d) (at least as far as the language is concerned).

Finally, let's clean up this mess:

d->~Derived();
std::free(addr);

The opportunity to destroy the original *b has passed, so we may have leaked that.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文