首页 > 解决方案 > 具有原子成员的单例类的实现

问题描述

我正在尝试实现一个类来跟踪多线程代码的性能,并且遇到了以下问题。

跟踪器应该有一个实例,因此是单例模式。为了计算所有线程中的对象创建和函数执行,我认为使用原子成员是个好主意。但我似乎无法找出正确的实现。

这是我想做的最小代码:

#include <atomic>
#include <iostream>

class Counter
{
public:
    static Counter& instance()
    {
        return instance_;
    };

    void increment()
    {
        counter_++;
    };

private:
    Counter ()
    {
        std::cout << "ctor counter" << std::endl;
    };

    ~Counter ()
    {
        std::cout << "counter_: " << counter_ << std::endl;
        std::cout << "dtor counter" << std::endl;
    }

    static Counter instance_;
    std::atomic<int> counter_{0};
    //int counter_ = 0;
};

Counter Counter::instance_ = Counter();

int main(void)
{
    Counter::instance().increment();
    Counter::instance().increment();
    Counter::instance().increment();

    return 0;
}

如果counter_变量是int,它可以正常工作,但不是线程安全的。如果是atomic<int>,那么编译器会告诉我:

   g++ foo.cc
foo.cc:34:38: error: use of deleted function 'Counter::Counter(const Counter&)'
 Counter Counter::instance_ = Counter();
                                      ^
foo.cc:4:7: note: 'Counter::Counter(const Counter&)' is implicitly deleted because the default definition would be ill-formed:
 class Counter
       ^~~~~~~
foo.cc:4:7: error: use of deleted function 'std::atomic<int>::atomic(const std::atomic<int>&)'
In file included from foo.cc:1:0:
/usr/include/c++/7/atomic:668:7: note: declared here
       atomic(const atomic&) = delete;
       ^~~~~~

我不确定我是否完全理解这个问题。任何解释/解决方案将不胜感激。

干杯

标签: c++multithreadingstdatomic

解决方案


您可以通过std::atomic<int> counter_{0};简单地成为static类成员而不是每个实例的一部分来简化。(因为您要确保该类只有一个实例。)

或者,如果您只是以这种方式使用“单例”返回对静态对象的引用,只需创建其所有成员static,这样您就不需要首先获取指向它的单个实例的指针。然后它可以被namespace{}公有和私有静态成员函数美化。我认为“单例”的唯一要点是通过使用带有非常量初始化程序的函数范围静态变量来延迟初始化它直到静态初始化之后,但你没有这样做。


实际问题出在类的复制构造函数中,您的静态初始化程序使用您编写它的方式。 它构造一个临时变量Counter();,然后将其复制到静态instance_变量。

您可以编译为 C++17,保证删除该副本在 Godbolt 上使用 g++-std=gnu++17且源代码不变),或者您可以重写初始化程序

Counter Counter::instance_;    // default construct
Counter Counter::instance_{};  // explicitly default construct

两者都适用于g++-std=gnu++11


推荐阅读