为什么单例模板会崩溃我的程序?

时间:2016-05-25 18:12:51

标签: c++ templates crash singleton

以下是定义模板的头文件: 基于几个答案,我做了修改,使模板单线程安全。但程序仍然崩溃,所以我认为如果模板是线程安全的,那么重点不应该是。它讲述了当C1从CBase和Singleton继承时发生的事情。显然,当C1试图调用其父类CBase的成员变量的函数时,程序崩溃。在我看来,当多个继承和单个模板一起使用时,内存中会出现混乱。

#ifndef _T_SINGLETON_HH_
#define _T_SINGLETON_HH_

template <class T>
class Singleton
{
public:
    static T*   getInstance()
    {   
        static T instance_;
        return &instance_;
    }   

    template <class A>
    static T*   getInstance(A a)
    {   
        static T instance_(a);
        return &instance_;
    }   

protected:
    Singleton() {}
    virtual ~Singleton() {}
private:
    Singleton(Singleton const&);
    Singleton& operator=(Singleton const&);
};
#endif  // _T_SINGLETON_HH_ //

以下是此模板的使用方法:

#include <iostream>

#include <unistd.h>
#include <pthread.h>

#include "singleton.hh"

using namespace std;

class X;
class X1; 
class ABase;
class BBase;
class BB; 
class CBase;

class X { 
public:
    virtual int getX() = 0;
};

class X1 : public X { 
public:
    int getX() { return 1; }
};

class Timer
{
private:
   static Timer* t_; 
   BBase* b_; 

   Timer();
   static void* go_helper(void* context);
   void go();

public:
    virtual ~Timer() {}

    static Timer* getInstance();
    void subscribe(BBase* b); 
    void unsubscribe(BBase* b) { b_ = NULL; }
};

class CBase {
public:
    CBase(BB& bb) : bb_(bb) {}
    virtual void run() = 0;
protected:
    BB& bb_;
};

class C1 : public CBase,
           public Singleton<C1>
{
public:
    C1(BB& bb) : CBase(bb) {}
    void run();
};

class BBase {
public:
    BBase(ABase& a) : a_(a) { Timer::getInstance()->subscribe(this); } 
    virtual ~BBase() { Timer::getInstance()->unsubscribe(this); }
    virtual void run() = 0;
protected:
    ABase&  a_; 
};

class BB : public BBase {
public:
    BB(ABase& a) : BBase(a) {
        c = C1::getInstance(*this);
//      c = new C1(*this);    IF WE USE THIS INSTEAD, THEN IT WILL WORK
    }
    int getX();
    void run();
private:
    CBase*  c;
};

class ABase {
public:
    ABase() {
        x = new X1;
        b = new BB(*this);
    }
    void run();
    int getX() { return x->getX(); }
private:
    X* x;
    BBase* b;
};

Timer* Timer::t_ = NULL;

Timer::Timer() : b_(NULL)
{
    pthread_t th;
    pthread_create(&th, NULL, &Timer::go_helper, this);
}

Timer*
Timer::getInstance() {
    if (t_ == NULL)
        t_ = new Timer;
    return t_;
}

void
Timer::subscribe(BBase* b) { b_ = b; }

void*
Timer::go_helper(void* context) {
    Timer *t = reinterpret_cast<Timer*>(context);
    t->go();
    return NULL;
}

void
Timer::go()
{
    while(1) {
        sleep(1);
        if (b_) b_->run();
    }
}

void ABase::run() {
    cout << __PRETTY_FUNCTION__ << getX() << endl;
    cout << __PRETTY_FUNCTION__ << x->getX() << endl;
    b->run();
    while(1)
        sleep(1);
}

int BB::getX() {
    return a_.getX();
}
void BB::run() {
    cout << __PRETTY_FUNCTION__ << endl;
    c->run();
}

void C1::run() {
    cout << __PRETTY_FUNCTION__ << bb_.getX() << endl;
}

int main()
{
    ABase*  a = new ABase;
    a->run();
}

如果您仍然认为这是一个线程安全的问题,那么这里是使用该模板的简化的一个线程版本:

#include <iostream>

#include <unistd.h>
#include <pthread.h>

#include "singleton.hh"

using namespace std;

class X;
class X1; 
class ABase;
class BBase;
class BB; 
class CBase;

class X { 
public:
    virtual int getX() = 0;
};

class X1 : public X { 
public:
    int getX() { return 1; }
};

class CBase {
public:
    CBase(BB& bb) : bb_(bb) {}
    virtual void run() = 0;
protected:
    BB& bb_;
};

class C1 : public CBase,
           public Singleton<C1>
{
public:
    C1(BB& bb) : CBase(bb) {}
    void run();
};

class BBase {
public:
    BBase(ABase& a) : a_(a) { } 
    virtual ~BBase() { } 
    virtual void run() = 0;
protected:
    ABase&  a_; 
};

class BB : public BBase {
public:
    BB(ABase& a) : BBase(a) {
        c = C1::getInstance(*this);
//      c = new C1(*this);
    }   
    int getX();
    void run();
private:
    CBase*  c;  
};

class ABase {
public:
    ABase() {
        x = new X1; 
        b = new BB(*this);
    }   
    void run();
    int getX() { return x->getX(); }
private:
    X* x;
    BBase* b;
};

void ABase::run() {
    cout << __PRETTY_FUNCTION__ << getX() << endl;
    cout << __PRETTY_FUNCTION__ << x->getX() << endl;
    b->run();
    while(1)
        sleep(1);
}

int BB::getX() {
    return a_.getX();
}
void BB::run() {
    cout << __PRETTY_FUNCTION__ << endl;
    c->run();
}

void C1::run() {
    cout << __PRETTY_FUNCTION__ << bb_.getX() << endl;
}

当我运行此程序时,它崩溃了:

void ABase::run()1
void ABase::run()1
virtual void BB::run()
zsh: segmentation fault (core dumped)  ./a.out

以下是GDB提供的堆栈信息:

#0  0x00000000004012ba in ABase::getX (this=0x1) at tst_sigill.cc:89
89        int getX() { return x->getX(); }
[Current thread is 1 (Thread 0x7faa0f050740 (LWP 5351))]
(gdb) bt
#0  0x00000000004012ba in ABase::getX (this=0x1) at tst_sigill.cc:89
#1  0x0000000000400e0c in BB::getX (this=0x7ffed6e821f0) at tst_sigill.cc:138
#2  0x0000000000400e71 in C1::run (this=0xaeedd0) at tst_sigill.cc:146
#3  0x0000000000400e51 in BB::run (this=0xaeec60) at tst_sigill.cc:142
#4  0x0000000000400de3 in ABase::run (this=0xaeec20) at tst_sigill.cc:132
#5  0x0000000000400ed1 in main () at tst_sigill.cc:152

由于某些未知原因,传递给BB :: getX和ABase :: getX的指针搞砸了。我无法看清代码的错误。

2 个答案:

答案 0 :(得分:2)

好吧,主要问题似乎是你的getInstance()实现不是线程安全的。

我建议忘记所有construct() / destruct()内容,并将其留在ABI提供的CRT0代码中。

您应该使用保证为线程安全的Scott Meyer's Singleton之后的实现(至少对于当前标准而言):

template <class T>
class Singleton {
public:
    static T& getInstance() {   
        static T instance_;
        return instance_;
    }   

protected:    
    Singleton() {}
    virtual ~Singleton() {}

    Singleton(Singleton const&) = delete;
    Singleton& operator=(Singleton const&) = delete;
};

如果以后需要更改任何可以异步访问的Singleton属性,请记住提供线程安全操作。

您可以使用模板单例基础支持,例如:

template <class T>
class Singleton {
   // ...
protected:    
    mutable std::mutex internalDataGuard;
};

class C1 : public CBase, public Singleton<C1> {
public:
     C1(BB& bb) : CBase(bb) {}
     void run();

     void setSomeData(T data) {
         std::unique_lock(Singleton<C1>::internalDataGuard);
         // change internal data member ...
     }

     const T& getSomeData() const {
         std::unique_lock(Singleton<C1>::internalDataGuard);
         // return internal data member ...
     }
};

答案 1 :(得分:0)

使用堆栈跟踪来挑选一些有用信息的快速说明。程序在崩溃的地方崩溃,但有时跟踪可以给你一些关于事情开始出错的提示:

#0  0x00000000004012ba in ABase::getX (this=0x1) at tst_sigill.cc:89

死在这里。这个= 0x1,说this指的是胡说八道; 0x1几乎肯定是无效的内存位置。我想不出一个现代平台,其中1是有效内存。并不意味着没有一个,但这是极不可能的,因而是一个嫌疑人。

#1  0x0000000000400e0c in BB::getX (this=0x7ffed6e821f0) at tst_sigill.cc:138

此处this值得怀疑。在接下来的几行中,我们看到所有this指针都在0xaeeXXX中。 0x7ffed6e821f0相当远,所以我们应该首先查看对BB的引用

#2  0x0000000000400e71 in C1::run (this=0xaeedd0) at tst_sigill.cc:146

特别是那些在C1内的人,因为它是我们所处理的最后一个对象。

可能堆栈跟踪的其余部分很重要,也许它没有。也许我们会回过头来寻找更多线索,但是暂时让我们减少目前的线索。

查看C1,我看到了:

C1(BB& bb) : CBase(bb) {}

采用BB引用并将其传递给CBase的构造函数

CBase(BB& bb) : bb_(bb) {}

BB存储在bb_

那么BB来自哪里? C1由单身人士实例化。我没有看到对construct的任何调用,因此这会留下getInstance(A a),但不会引用它。这意味着C1(BB& bb)被临时调用。临时文件最终存储在CBase::bb_ BB&中,因此bb_是对从getInstance返回后将不再存在的变量的引用。在此之后尝试使用bb_将调用未定义的行为。

我不打算为此提出修正,因为这段代码太过于复杂,因为它的好处,正如πάνταῥεῖ在他的回答中所指出的那样,如果没有重大的重新设计,它就无法发挥作用。