使用CxxTest损坏的单例数据

时间:2010-08-31 16:38:30

标签: c++ static singleton coredump

这是一个奇怪的问题,我不知道该怎么做。

我有以下内容:

struct Parms
{
    const std::string value1;
    const std::string value2;

    std::string parm1;
    std::string parm2;

    Parms() : parm1(value1), parm2(value1) {}

    static const Parms& getDefaults()
    {
        static Parms defaults;
        return defaults;
    }
};

我通常会这样使用:

Parms myParms = Parms::getDefaults();
myParms.parm1 = "crap";
functionThatNeedsParms(myParms);

非常简单。在我开始尝试使用CxxTest编写使用此代码的单元测试之前,这从未引起任何麻烦。我在不同的文件中有两个测试套件类,当我单独运行它们时,一切都很棒。

当我一起跑,我看到两件坏事。首先,整个核心转储尝试加倍释放静态默认值变量。其次,如果我查看默认值的内容,它会在它死之前的某个时间,但是在我开始使用它之后,那里的静态const std :: strings已经损坏(有些字母已经随机改变了,尽管它始终是每次运行都一样。)

发生了什么事?

3 个答案:

答案 0 :(得分:2)

双重免费和核心转储

我想我可以解释你所遇到的“双重免费核心转储”问题。我最近遇到了同样的事情,听起来你做的和我做的一样。

根据你的描述,你说当你“单独运行它们”时它们工作正常,但是如果你“一起运行它们”你会得到双重免费/核心转储问题。

如果同一个全局声明两次,我发现会发生这种情况。

在我的情况下,我有一个类foo,在一个文件中我有一个全局class foo gFoo;,在另一个文件中我有一个全局class foo gFoo;。 (是的,这听起来很愚蠢,实际上我是链接文件X.cxx以及包含X.cxx的共享库 - 结果基本相同。)

现在,我原本期望编译器对此抱怨,但显然有标志可以启用或禁用此检查,并且代码编译正常。但是当程序终止并调用它的所有析构函数时,它会两次调用gFoo的析构函数并给我双重自由消息以及核心转储。

鉴于你声明它可以独立工作但在组合时失败,我打赌你在两个单独的文件中定义全局,并且当它们自己编译时工作正常,但当你将它们组合成一个单独的时候测试,你可能有两次全局声明。

检查出来。

答案 1 :(得分:0)

C和C ++中的静态变量不是线程安全的。这意味着如果两个线程试图访问你的单例对象,就会发生竞争条件(坏事)。解决问题的一种方法是使用线程本地存储。这是由pthreads库支持的,一些编译器也直接支持线程本地存储。

另一种方法是,如果你的单例必须是所有线程的全局,那就是提供锁定以确保一次只有一个线程可以访问你的数据。

然而,从那时起,问题才出现在单元测试中。我建议不要运行多线程单元测试,除非你打算在多线程中使用你的单例。

答案 2 :(得分:-1)

这高度依赖于您正在使用的编译器和平台,并且实际上没有看到测试我只能猜测发生了什么。

我在你的代码中看到了一些误解:
1)您缺少复制操作符和复制构造函数 您正在复制包含std::string的实例,这些实例可能使用引用计数来实现。引用计数是在std::string的重载复制构造函数/运算符中实现的,但这些可能不会从类的隐式生成的复制构造函数中调用,因此会导致双重释放内存和其他令人讨厌的事情。复制操作符/构造函数应如下所示:

// Copy constructor
Parms(const Parms& oth)  { parm1 = oth.parm1; parm2 = oth.parm2; }

// Copy operator
Parms& operator= (const Parms& oth)  { 
  if (&oth == this)  // Check for self-assignment
    return *this;
  parm1 = oth.parm1;
  parm2 = oth.parm2;
  return *this;
}



2)我不太了解value1value2的存在。您似乎永远不会初始化它们,只需在默认构造函数中使用它们将其(空)内容复制到parm1parm2。您可以完全避免这种情况,因为parm1parm2在被调用时会自动初始化为空字符串。

3)您不需要在这里使用单例模式。 getDefaults()方法可以按如下方式实现:

static Parms getParms() { return Parms(); }

单例模式适用于在整个程序运行中只有一个实例的类,而这似乎不是您的类。

通过这种方式,您可以安全地使用多个线程中的getParms()函数,智能编译器将优化隐含的附加副本。