以下是定义模板的头文件: 基于几个答案,我做了修改,使模板单线程安全。但程序仍然崩溃,所以我认为如果模板是线程安全的,那么重点不应该是。它讲述了当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的指针搞砸了。我无法看清代码的错误。
答案 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_
将调用未定义的行为。
我不打算为此提出修正,因为这段代码太过于复杂,因为它的好处,正如πάνταῥεῖ在他的回答中所指出的那样,如果没有重大的重新设计,它就无法发挥作用。