以下是前向声明的另一个例子,可能是 如果应用程序需要一个自我维持的对象数组,则非常有用 它可以在运行时添加和删除自身:
文件a.h:
#include "a.h" A *A::first=0, *A::last=0; // don't put the word static here, that will cause an error A::A() { if(first==0) first=this; //first A created previous=last; if(previous != 0) previous->next=this; last=this; next=0; } A::~A() { if(previous != 0) previous->next=next; if(next != 0) next->previous=previous; }
档案a.cpp:
A
来自:https://en.wikipedia.org/wiki/Circular_dependency#Self-reference_example
我认为A
的实施存在问题。在构造A::first
的第一个实例时,如果某个其他线程引用{{1}},则会导致意外行为。如果我错了,请纠正。
此外,如何解决此问题?感谢。
答案 0 :(得分:2)
此代码绝对不是线程安全的,正如其他人在评论中所述。无论是第一个对象,它是2个线程同时尝试创建或删除A对象,您得到未定义的行为,因为两个不同的线程使用并更改相同的静态值而没有任何同步。
可以做些什么?一如既往两个相同的选择:
正如@zvone在评论中注意到的那样,当您删除链的第一个或最后一个元素时,析构函数可以使first
和last
成为悬空指针。
析构函数(非线程安全版本)应为:
A::~A() {
if(previous != 0) previous->next=next;
if(next != 0) next->previous=previous;
if (first == this) first = next;
if (last == this) last = previous;
}
线程安全版本可以是:
档案a.h
class A {
public:
static A *first, *last;
A *previous, *next;
static std::mutex mut;
A();
~A();
};
档案a.cpp:
#include "a.h"
A *A::first=0, *A::last=0; // don't put the word static here, that will cause an error
std::mutex A::mut;
A::A() {
mut.lock()
if(first==0) first=this; //first A created
previous=last;
if(previous != 0) previous->next=this;
last=this;
next=0;
mut.unlock();
}
A::~A() {
mut.lock()
if(previous != 0) previous->next=next;
if(next != 0) next->previous=previous;
if (first == this) first = next;
if (last == this) last = previous;
mut.unlock();
}
答案 1 :(得分:1)
这里的答案问题是它们没有指出上一个和下一个是公共成员的事实 - 这意味着构造函数和析构函数中的互斥体没有保护可以使类完全线程安全。使这个类线程安全的唯一方法是隐藏这些成员并提供访问它们的方法 - 每个成员都有一个同步原语。
答案 2 :(得分:0)
您需要将first
和last
变量声明为原子,例如std::atomic
或者使用互斥锁保护它们
答案 3 :(得分:0)
是的,这是对的。他们需要受到抗议。此外,previous
和next
也需要受到保护。 e.g。
A::~A() {
unique_lock<std::mutex> locked(A::A_mutex);
if(previous != 0) previous->next=next;
if(next != 0) next->previous=previous;
}
其中A_mutex
是静态成员。