【问题标题】:When is an overridden virtual function not called什么时候不调用被覆盖的虚函数
【发布时间】:2020-01-01 20:56:07
【问题描述】:

我正在使用 MFC 的 CRecordset 类。我已经覆盖了虚拟方法DoBulkFieldExchange

一般来说,我的重写方法被调用并且工作得很好。但是,我有调用基类DoBulkFieldExchange方法的情况。

在我派生的CRecordset 类的一个无关方法中,我调用AfxThrowDBException。这调用了CRecordset 析构函数。而且,在清理过程中,DoBulkFieldExchange 在基类中被调用,而不是在我的派生类中。在这种情况下,它会导致断言,因为基类不希望使用此配置调用默认版本。

我知道我的派生类设置正确,因为它被调用了。那么什么情况下会调用基类的方法呢?

这是我的自定义 CRecordset 类:

class CRS : public CRecordset
{
public:
    int m_nId;
    TCHAR m_szName[CUSTOMER_NAME_MAXLENGTH + 1];

    int* m_pnIds;
    long* m_pnIdLengths;
    LPTSTR m_pszNames;
    long* m_pnNameLengths;

public:
    CRS(CDatabase* pDatabase = NULL)
        : CRecordset(pDatabase)
    {
        m_nFields = 2;

        m_nId = 0;
        m_szName[0] = '\0';

        m_pnIds = NULL;
        m_pnIdLengths = NULL;
        m_pszNames = NULL;
        m_pnNameLengths = NULL;
    }

    CString GetDefaultSQL()
    {
        return CCustomerData::m_szTableName;
    }

    void DoFieldExchange(CFieldExchange* pFX)
    {
        pFX->SetFieldType(CFieldExchange::outputColumn);
        RFX_Int(pFX, _T("Id"), m_nId);
        RFX_Text(pFX, _T("Name"), m_szName, CUSTOMER_NAME_MAXLENGTH);
    }

    void DoBulkFieldExchange(CFieldExchange* pFX)
    {
        pFX->SetFieldType(CFieldExchange::outputColumn);
        RFX_Int_Bulk(pFX, _T("Id"), &m_pnIds, &m_pnIdLengths);
        RFX_Text_Bulk(pFX, _T("Name"), &m_pszNames, &m_pnNameLengths, (CUSTOMER_NAME_MAXLENGTH + 1) * 2);
    }
};

这是使用它的代码。 ExecuteSqlQuery 只需使用给定的数据库调用 CRS::Open()

CRemoteDatabase db;
db.Open();
auto prs = db.ExecuteSqlQuery<CRS>(NULL, CRecordset::forwardOnly, CRecordset::useMultiRowFetch);

while (!prs->IsEOF())
{
    // The call to GetFieldValue is producing an 'Invalid cursor position'
    // error, which causes AfxThrowDBException to be called. This
    // indirectly calls the destructor, which then calls the base-class
    // DoBulkFieldExchange method, which in turn ASSERTs. Why doesn't
    // it call my derived method?
    CString sValue;
    prs->GetFieldValue((short)CUSTOMER_ID, sValue);
}

【问题讨论】:

标签: c++ inheritance mfc virtual-functions


【解决方案1】:

当基类析构函数调用虚函数时,可能会调用基类方法而不是派生类方法。在这种情况下,派生类已经被销毁,不能调用任何虚方法。 (更多信息请访问this question)。

回到你的问题:

来自 MFC 代码,使用 VS 2019 (14.22.27905) 的 dbcore.cpp:

CRecordset::FreeRowset() 调用DoBulkFieldExchange,看起来这是在某些情况下FreeRowset()CRecordset 的析构函数调用。

这是CRecordset::FreeRowset代码的评论。

调用虚函数,DoBulkFieldExchange,这里不好 因为Close 然后FreeRowset 可能会被析构函数调用。 但是,如果 RFX_Bulk 函数可以,则没有简单的选择 内存分配。最终结果是用户必须调用 Close 明确 (而不是依赖析构函数)如果使用多行提取, 否则他们会发生内存泄漏。如果行集已经分配, 删除旧的行集缓冲区

【讨论】:

  • 所以,这是因为它是从析构函数中调用的,而派生类已经被析构了?如果我在抛出异常之前调用Close(),那么我不会得到 ASSERT。但后来我也丢失了错误信息。有点乱,但我认为这是正确的答案。
  • 顺便说一句,这是一个很好的答案。 this question 有 250 点赏金,如果您有兴趣。它与相同的代码有关。
  • 是的,基类的析构函数的调用顺序与其构造函数的完成顺序相反。所以如果你的基类调用虚函数,则派生对象的析构函数已经被调用,所以派生类的函数不能被调用。
猜你喜欢
  • 1970-01-01
  • 2018-03-08
  • 2013-09-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-01
  • 2012-02-24
相关资源
最近更新 更多