0

I'm implementing a Singleton template in C++. I try to achieve thread-safe by std::call_once and std::once_flag, but somehow link error happens.

singleton.h

#ifndef _SINGLETON_H_
#define _SINGLETON_H_

#include <boost/noncopyable.hpp>
#include <mutex>

template<typename T>
class Singleton : boost::noncopyable {
public: 
    Singleton() = delete;

    static T& getInstance() {
        std::call_once(init_flag_, &Singleton::init);
        return *val_;
    }

private:
    static void init() {
        val_ = new T();
    }

private:
    static std::once_flag init_flag_;
    static T* val_; 
};

#endif // _SINGLETON_H_

test_singleton.cc

#include "singleton.h"
#include <iostream>

class Log {
public:
    void log() {
        std::cout << "log" << std::endl;
    }
};

int main() {
    Log & logger = Singleton<Log>::getInstance();
    logger.log();
}

And my g++ statement is

g++ -std=c++14 -pthread -o test test_singleton.cc

Error message:

/tmp/ccoxQBXl.o: In function `Singleton<Log>::getInstance()':
test_singleton.cc:(.text._ZN9SingletonI3LogE11getInstanceEv[_ZN9SingletonI3LogE11getInstanceEv]+0x2c): undefined reference to `Singleton<Log>::init_flag_'
test_singleton.cc:(.text._ZN9SingletonI3LogE11getInstanceEv[_ZN9SingletonI3LogE11getInstanceEv]+0x38): undefined reference to `Singleton<Log>::val_'
/tmp/ccoxQBXl.o: In function `Singleton<Log>::init()':
test_singleton.cc:(.text._ZN9SingletonI3LogE4initEv[_ZN9SingletonI3LogE4initEv]+0x11): undefined reference to `Singleton<Log>::val_'
collect2: error: ld returned 1 exit status
Tinyden
  • 524
  • 4
  • 13
  • Totally unrelated to your problem, but please note that all symbols starting with an underscore and followed by an upper-case letter (like for example `_SINGLETON_H_`) are reserved in all scopes. Please see [What are the rules about using an underscore in a C++ identifier?](https://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-a-c-identifier) for details. – Some programmer dude Jul 06 '20 at 04:47
  • As for the problem, it's the usual when having `static` member variables: In the class they are only *declared*, you need to *define* them as well. Usually this is done in a single source file, but that's hard with templates. If your compiler is new enough to have C++17, then you could make them `inline`, otherwise define them in the header file outside of the class. – Some programmer dude Jul 06 '20 at 04:49
  • Oh That's it! Silly me. – Tinyden Jul 06 '20 at 04:52
  • 1
    I change c++ standard to C++17. And static inline std::once_flag init_flag_{}; static inline T* val_ = nullptr; – Tinyden Jul 06 '20 at 04:53
  • The easiest and the safest implementation of the Singleton is the *Meyers' Singleton* (if you use C++11 or higher). Are you sure you want to reinvent the wheel with the `std::call_once`? – Dmitry Kuzminov Jul 06 '20 at 05:24
  • boost::nocopyable::init_flag – GobeRadJem32 Jul 06 '20 at 06:49

1 Answers1

1

Finally got it. The only problem is I didn't initialize the static variables. For C++17, inline keyword allows initialization within class.

#ifndef SINGLETON_H_
#define SINGLETON_H_

#include <boost/noncopyable.hpp>
#include <mutex>

template<typename T>
class Singleton : boost::noncopyable {
public: 
    Singleton() = delete;

    static T& getInstance() {
        std::call_once(init_flag_, &Singleton::init);
        return *val_;
    }

private:
    static void init() {
        val_ = new T();
    }

private:
    static inline std::once_flag init_flag_{};
    static inline T* val_ = nullptr; 
};

#endif // SINGLETON_H_
Tinyden
  • 524
  • 4
  • 13