在构造函数中引用此指针

时间:2015-12-14 12:45:46

标签: c++ multithreading constructor this self-reference

  

以下是前向声明的另一个例子,可能是   如果应用程序需要一个自我维持的对象数组,则非常有用   它可以在运行时添加和删除自身:

     

文件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}},则会导致意外行为。如果我错了,请纠正。

此外,如何解决此问题?感谢。

4 个答案:

答案 0 :(得分:2)

此代码绝对不是线程安全的,正如其他人在评论中所述。无论是第一个对象,它是2个线程同时尝试创建或删除A对象,您得到未定义的行为,因为两个不同的线程使用并更改相同的静态值而没有任何同步。

可以做些什么?一如既往两个相同的选择:

  • 将类(至少是构造函数和析构函数)记录为不是线程安全的。因为它们是调用者的责任,以确保只有一个线程可以同时访问A对象。换句话说,这意味着A只在单个程序中安全。
  • 在类本身内添加同步以使其线程安全。由于创建和销毁操纵静态成员,因此需要为类的所有对象提供全局互斥锁,例如,不同的是静态类。

正如@zvone在评论中注意到的那样,当您删除链的第一个或最后一个元素时,析构函数可以使firstlast成为悬空指针。

析构函数(非线程安全版本)应为:

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)

您需要将firstlast变量声明为原子,例如std::atomic
或者使用互斥锁保护它们

答案 3 :(得分:0)

是的,这是对的。他们需要受到抗议。此外,previousnext也需要受到保护。 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是静态成员。