【问题标题】:Inheriting from CString caused out of memory exception从 CString 继承导致内存不足异常
【发布时间】:2014-03-11 08:59:00
【问题描述】:

我有以下从 MFC CString 继承的字符串类

class TString : public CString
{
public:
    TString() : CString(_T(""))
    {
    }

    TString(LPCTSTR str) : CString(str)
    {
    }
};

我在一个经常使用带有 TString 的 + 运算符的方法中出现内存不足异常,所以我尝试进行如下测试

TString str;
TCHAR buffer[] = "Hello world, Hello world, Hello world, Hello world, Hello world, Hello world";

uint i = 0;
while(i++ < 100000000)
{
    str = buffer;
    str += buffer;
}

占用大量内存并以内存不足异常结束这是在执行最后一个代码后从任务管理器更改内存的镜头

当我用 CString 替换 TString 时,这是正常的花费时间并且没有内存不足异常,并且内存在任务管理器中是稳定的,就像这张照片一样

我尝试了以下两种状态

  1. 我创建了另一个 exe 应用程序,它依赖于包含 TString 类的同一个库,它像 CString 一样工作得非常好
  2. 我在 while 循环中调用了 Sleep(1),它导致内存快速变化,并且也很稳定 我该怎么做才能解决这样的问题??

编辑:

当我重新编译解决方案时,我的解释中有一些错误 CString 也有相同的行为,即使是 std::string 我认为新的运算符重载我创建了一个自定义类,它调用 malloc 并在析构函数中释放它也导致对于相同的行为,我将该测试代码移动到我的应用程序的第一个入口点到我的应用程序的构造函数,该构造函数继承自 CWinAppEx 代码工作正常然后我查看了 InitInstance 我发现内存泄漏检测 4 行代码如下

int tmpDbgFlag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);
tmpDbgFlag |= _CRTDBG_DELAY_FREE_MEM_DF;//<====this is the evil after comment everything is fine
tmpDbgFlag |= _CRTDBG_LEAK_CHECK_DF;
_CrtSetDbgFlag(tmpDbgFlag);

我一直都知道这是人为的错误。这会导致灾难。

我刚刚评论了这一行

tmpDbgFlag |= _CRTDBG_DELAY_FREE_MEM_DF;

解决了。 这个答案还谈到了这个标志的用法Finding heap corruption 我感谢大家试图帮助解决这个问题,我希望它会有所帮助。

【问题讨论】:

  • 1) 为什么要从 CString 继承? 2) 验证 CString-only 代码没有导致编译器优化掉那个循环。
  • @PaulMcKenzie:真的,我正在移植写在 OWL 库顶部的旧代码以使用 MFC,使用的字符串类型是 TString 并且有一些自定义方法,它在代码中被大量使用,所以我让它继承自 CString 并提供这些方法,现在软件正在测试中,这是一个错误。
  • 当你继承一个类你需要重新定义所有的构造函数和操作符,我认为
  • 顺便说一句,创建一个 typedef 怎么样?
  • @cha:我说最大的问题是其他代码中大量使用了 Tstring 的自定义方法,而 CString 中没有。并且操作符已经被继承了,但是我定义了调用父 CString 构造函数的构造函数。

标签: c++ mfc out-of-memory cstring


【解决方案1】:

无论如何,从没有虚拟析构函数的类继承是一个非常糟糕的主意。以下引自 Scott Meyers 的“Effective C++: 55 Specific Ways to Improvement Your Programs and Designs, Third Edition”。

问题是getTimeKeeper返回一个指向派生类的指针 对象(例如,AtomicClock),该对象正在通过基础删除 类指针(即 TimeKeeper* 指针)和基类 (TimeKeeper) 有一个非虚拟析构函数。这是一个食谱 灾难,因为 C++ 指定当派生类对象是 通过指向具有非虚拟基类的指针删除 析构函数,结果未定义。运行时通常会发生什么 是对象的派生部分永远不会被破坏。如果来电 getTimeKeeper 碰巧返回了一个指向 AtomicClock 的指针 对象,对象的 AtomicClock 部分(即数据成员 在 AtomicClock 类中声明)可能不会被销毁, AtomicClock 析构函数也不会运行。但是,基类部分 (即 TimeKeeper 部分)通常会被破坏,从而导致 到一个奇怪的“部分毁坏”的物体。这是一个很好的方法 泄漏资源,损坏数据结构,并花费大量时间 一个调试器。

总而言之,当您从没有虚拟析构函数的类派生时,然后使用指向该对象的指针,当您删除该指针时,该对象仅被部分销毁。这显然只发生在指针指向基类而不是指针指向类本身时。

如果你必须扩展一个没有虚拟析构函数的类,最好通过包含来实现。那就是创建一个不是从您希望扩展的类派生的新类。然后有一个您希望扩展的类型的成员变量,并实现您希望扩展的类中的所有函数,作为您希望扩展的类中适当函数的包装器。

例如。

class TString /* do not derive from CString */
{
private:
  CString m_string;
    TString() :
      m_string()
  {
  }

  TString(const TCHAR *str) :
    m_string(str)
  {
  }

  int GetLength() const
  {
    return m_string.GetLength();
  }

  /* All the other functions in the CString class here. */

  /* Your additions to the CString class here. */
}

当然,在最近的 MFC 版本中,CString 实际上是一个模板类,因此您或许应该将您的类设为模板类,如下所示。

template< typename BaseType >
class TStringT
{
    /* The same as above but use BaseType instead of TCHAR. */
}

然后执行以下操作。

typedef TStringT< wchar_t > TStringW;
typedef TStringT< char > TStringA;
typedef TStringT< TCHAR > TString;

我希望这会有所帮助。

【讨论】:

  • 如你所见,我没有处理指针,变量被声明为 TString,我没有析构函数,也没有任何关系
  • 我编辑的问题添加了问题的原因和解决方案
【解决方案2】:

这可能不是您问题的答案,但请尝试一下。

在您的 TString 类中添加以下方法:

TString& operator=(LPCTSTR src)
{
    CString::operator=(src);

    return( *this );
}

这将加快从 LPTSTR(例如 str = buffer;)分配 TString 的速度。如果没有从 LPTSTR 分配 TString 的该方法,将在执行分配之前构造一个临时 TString。如果您使用调试器逐步完成分配,您会看到。

【讨论】:

  • 我编辑的问题添加了问题的原因和解决方案
猜你喜欢
  • 2012-12-15
  • 2011-05-26
  • 2013-06-22
  • 2013-12-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-22
相关资源
最近更新 更多