C++ 在销毁派生类后使用基类的成员

C++ 在销毁派生类后使用基类的成员,c++,language-lawyer,destructor,C++,Language Lawyer,Destructor,假设我们有一个简单的结构: struct RefCounters { size_t strong_cnt; size_t weak_cnt; RefCounters() : strong_cnt(0), weak_cnt(0) {} }; 从实现的角度来看,析构函数RefCounters::~RefCounters应该什么都不做,因为它的所有成员都具有基元类型。这意味着,如果使用析构函数的显式调用销毁了此类型的对象(但其内存未释放),那么在对象死亡后,我们将能够正常处理

假设我们有一个简单的结构:

struct RefCounters {
    size_t strong_cnt;
    size_t weak_cnt;
    RefCounters() : strong_cnt(0), weak_cnt(0) {}
};
从实现的角度来看,析构函数
RefCounters::~RefCounters
应该什么都不做,因为它的所有成员都具有基元类型。这意味着,如果使用析构函数的显式调用销毁了此类型的对象(但其内存释放),那么在对象死亡后,我们将能够正常处理其成员

现在假设我们还有一些从
RefCounters
派生的类。假设
RefCounters
派生的
类的基类中正好存在一次。假设为类
派生的对象显式调用析构函数,但其内存未释放。之后可以访问成员
强\u cnt
弱\u cnt

从实现的角度来看,它应该是可以的,至少在不涉及虚拟继承的情况下是可以的。因为
派生的*
可以静态地强制转换到
引用计数器*
(将编译时常量偏移量添加到地址),并且
派生的
类的析构函数不应触及
引用计数器
的内存

下面是一个代码示例:

struct RefCounted : public RefCounters {
    virtual ~RefCounted() {}
};

struct Base : public RefCounted {
    int val1;
    virtual void print();
};

struct Derived : public Base {
    std::string val2;
    virtual void print();
};

Derived *pDer = new Derived();
pDer->~Derived();          //destroy object
pDer->strong_cnt++;        //modify its member
std::cout << pDer->strong_cnt << pDer->weak_cnt << "\n";
struct RefCounted:公共RefCounters{
虚拟~RefCounted(){}
};
结构基:公共引用计数{
int val1;
虚拟空打印();
};
结构派生:公共基{
std::字符串val2;
虚拟空打印();
};
派生*pDer=新派生();
pDer->~派生()//摧毁目标
pDer->strong_cnt++//修改其成员

我认为你的方法不好。评论中有一个很好的链接,显示了关于标准细节的争论。一旦出现争论,不同的编译器很有可能以不同的方式实现这个细节。甚至更多。同一个编译器可能会将其实现从一个版本更改为另一个版本

你越多地使用各种暗角,你遇到问题的机会就越大


底线。我们愿意实现什么?为什么你不能用普通的C++语言来做这个?

我相信你的方法很差。评论中有一个很好的链接,显示了关于标准细节的争论。一旦出现争论,不同的编译器很有可能以不同的方式实现这个细节。甚至更多。同一个编译器可能会将其实现从一个版本更改为另一个版本

你越多地使用各种暗角,你遇到问题的机会就越大


底线。我们愿意实现什么?为什么不能使用普通C++语言来实现这一点?

因为<代码> Rebug St/Cuth>有一个平凡的析构函数,它的生命周期结束时,它的存储被重用或释放,每[Basic .Leave]/1结束。显式析构函数调用是无操作,不应影响任何内容;
RefCounters
的特定实例是更大对象的子对象这一事实也不应该存在。因此,
RefCounters
对象在
pDer->~Derived()之后应该仍然是活动的-但我相信通过
pDer
访问它会表现出未定义的行为。不过,类似的方法应该可以工作:
RefCounters*pRef=pDer;pDer->~派生();pRef->strong_cnt++另见:@stgatilov
static_cast(pDer)
被[basic.life]/(5.4)明确禁止:“在对象的生存期开始之前,但在分配对象将占用的存储之后,或者在对象的生存期结束之后,在重用或释放对象占用的存储之前,任何指向对象将位于或曾经位于的存储位置的指针都可以使用,但只能以有限的方式使用。。。程序具有未定义的行为,如果:。。。指针被用作
static\u cast
..“@stgatilov但是,
RefCounters*pRef=pDer;
应该是有效的,我认为。[basic.life]/(5.3)说:“…程序有未定义的行为,如果:。。。指针被隐式转换(4.10)为指向虚拟基类的指针。。。这似乎表明隐式转换为指向非虚拟基类的指针是可以的。事实上,据我所知,
virtual
一词是通过决议添加到本条款中的,目的是为了允许这种隐式转换。由于
RefCounters
有一个微不足道的析构函数,因此根据[basic.life]/1,当其存储被重用或释放时,其生存期结束。显式析构函数调用是无操作,不应影响任何内容;
RefCounters
的特定实例是更大对象的子对象这一事实也不应该存在。因此,
RefCounters
对象在
pDer->~Derived()之后应该仍然是活动的-但我相信通过
pDer
访问它会表现出未定义的行为。不过,类似的方法应该可以工作:
RefCounters*pRef=pDer;pDer->~派生();pRef->strong_cnt++另见:@stgatilov
static_cast(pDer)
被[basic.life]/(5.4)明确禁止:“在对象的生存期开始之前,但在分配对象将占用的存储之后,或者在对象的生存期结束之后,在重用或释放对象占用的存储之前,任何指向对象将位于或曾经位于的存储位置的指针都可以使用,但只能以有限的方式使用。。。程序具有未定义的行为,如果:。。。指针被用作
static\u cast
..“@stgatilov但是,
RefCounters*pRef=pDer;
应该是有效的,我认为。[basic.life]/(5.3)说:“…程序有未定义的行为,如果:。。。指针被隐式转换(4.10)为指向虚拟基类的指针……”,这似乎表明隐式转换为指向非虚拟基类的指针是可以的