How to pass argument in a singleton

情到浓时终转凉″ 提交于 2020-05-23 03:29:11

问题


I've been wondering how to pass argument to a singleton contructor. I already know how to do a singleton, but I've been unlucky to find a way to do it.

Here is my code (part of it).

Questionnary* Questionnary::getInstance(){

    static Questionnary *questionnary = NULL;

    if(questionnary == NULL){
        cout << "Object created";
        questionnary = new Questionnary();

    }
    else if(questionnary != NULL){
        cout << "Object exist";
    }

    return questionnary;
}

Questionnary::Questionnary(){
    cout << "I am an object";
}

//This is want i want to acheive
Questionnary::Questionnary(string name){
    cout << "My name is << name;
}

Many thanks in advance

(BTW i know how and why a singleton is bad)


回答1:


You don't need to allocate the instance of singleton dynamically. It could look the following way (this is sometimes called "lazy loading singleton" ~ the instance is created late & kinda "automatically"):

#include <iostream>
#include <string>

class Questionnary
{
private:
    // constructor taking string:
    Questionnary(const std::string& name) : name_(name) { }
public:
    static Questionnary& getInstance(const std::string& name)
    {
        static Questionnary q(name);
        std::cout << "My name is: " << q.name_ << std::endl;
        return q;
    }
private:
    std::string name_;
};

int main() {
    Questionnary::getInstance("Josh");
    Questionnary::getInstance("Harry");
}

output:

My name is: Josh
My name is: Josh

Note that constructor will be called only once right when the getInstance is called for the first time.




回答2:


Let me extend Martin York's answer for your use case. I recommend using pointer for argument(s) in this particular situation, as we make use of its inherent property, that it can be "empty".

class Questionnary
{
  std::string _str;

  static Questionnary& getInstanceImpl(std::string* const s = nullptr)
  {
    static Questionnary instance{ s };
    return instance;
  }

  Questionnary(std::string* const s)
    : _str{ s ? move(*s) : std::string{} } // employ move ctor
  {
    if (nullptr == s)
      throw std::runtime_error{ "Questionnary not initialized" };
  }

public:
  static Questionnary& getInstance()
  {
    return getInstanceImpl();
  }
  static void init(std::string s) // enable moving in
  {
    getInstanceImpl(&s);
  }

  Questionnary(Questionnary const&) = delete;
  void operator=(Questionnary const&) = delete;
};

I find this approach less confusing, as it let's you get the instance after first initialization without (anyway discarded) arguments:

// first init
Questionnary::init("my single Questionnary");

// later on ...
Questionnary& q = Questionnary::getInstance();

Edit: Removed argument from getInstance function and a few optimizations.




回答3:


Have a method to create the instance to pass arguments to the constructor and you could assert in the getInstance() method if CreateInstance has not been called prior to calling it. Like:

class Questionnary
{
private:
    // constructor taking string:
    Questionnary(const std::string& name) : name_(name) 
    {
        std::cout << "My name is: " << q.name_ << std::endl; 
    }

    static Questionnary* m_instance;
public:
    static void createInstance(const std::string& name)
    {
        assert(!m_instance);
        m_instance = new Questionary(name);
    }

    static void destroyInstance()
    {
        assert(m_instance);
        delete m_instance;
    }

    static Questionnary* Questionnary::getInstance()
    {
        assert(m_instance);
        return m_instance;
    }
private:
    std::string name_;
};



回答4:


My version using Modern C++ that wraps an existing type:

#ifndef SINGLETON_H
#define SINGLETON_H

template <typename C, typename ...Args>
class singleton
{
private:
  singleton() = default;
  static C* m_instance;

public:
  ~singleton()
  {
    delete m_instance;
    m_instance = nullptr;
  }
  static C& instance(Args...args)
  {
    if (m_instance == nullptr)
      m_instance = new C(args...);
    return *m_instance;
  }
};

template <typename C, typename ...Args>
C* singleton<C, Args...>::m_instance = nullptr;

#endif // SINGLETON_H

Here is what in looks like in a unit test:

  int &i = singleton<int, int>::instance(1);
  UTEST_CHECK(i == 1);

  tester1& t1 = singleton<tester1, int>::instance(1);
  UTEST_CHECK(t1.result() == 1);

  tester2& t2 = singleton<tester2, int, int>::instance(1, 2);
  UTEST_CHECK(t2.result() == 3);

The problem with this is that instance() requires arguments on each call but only uses them on the first (as noted above). Default arguments cannot be used generally. It may be better to use a create(Args...) method which must precede the call of instance() or throw an exception.




回答5:


//! @file singleton.h
//!
//! @brief Variadic template to make a singleton out of an ordinary type.
//!
//! This template makes a singleton out of a type without a default
//! constructor.

#ifndef SINGLETON_H
#define SINGLETON_H

#include <stdexcept>

template <typename C, typename ...Args>
class singleton
{
private:
  singleton() = default;
  static C* m_instance;

public:
  singleton(const singleton&) = delete;
  singleton& operator=(const singleton&) = delete;
  singleton(singleton&&) = delete;
  singleton& operator=(singleton&&) = delete;

  ~singleton()
  {
    delete m_instance;
    m_instance = nullptr;
  }

  static C& create(Args...args)
  {
    if (m_instance != nullptr)
      {
    delete m_instance;
    m_instance = nullptr;
      }
    m_instance = new C(args...);
    return *m_instance;
  }

  static C& instance()
  {
    if (m_instance == nullptr)
      throw std::logic_error(
        "singleton<>::create(...) must precede singleton<>::instance()");
    return *m_instance;
  }
};

template <typename C, typename ...Args>
C* singleton<C, Args...>::m_instance = nullptr;

#endif // SINGLETON_H

and:

void
singleton_utest::test()
{
  try
    {
      singleton<int, int>::instance();
      UTEST_CHECK(false);
    }
  catch (std::logic_error& e)
    {
      UTEST_CHECK(true);
    }

  try
    {
      UTEST_CHECK((singleton<int, int>::create(1) == 1));
      UTEST_CHECK((singleton<int, int>::instance() == 1));
    }
  catch (...)
    {
      UTEST_CHECK(false);      
    }

  using stester0 = singleton<tester0>;

  try
    {
      stester0::instance();
      UTEST_CHECK(false);
    }
  catch (std::logic_error& e)
    {
      UTEST_CHECK(true);
    }

  try
    {
      UTEST_CHECK((stester0::create().result() == 0));
      UTEST_CHECK((stester0::instance().result() == 0));
    }
  catch (...)
    {
      UTEST_CHECK(false);      
    }

  using stester1 = singleton<tester1, int>;

  try
    {
      stester1::instance();
      UTEST_CHECK(false);
    }
  catch (std::logic_error& e)
    {
      UTEST_CHECK(true);
    }

  try
    {
      UTEST_CHECK((stester1::create(1).result() == 1));
      UTEST_CHECK((stester1::instance().result() == 1));
    }
  catch (...)
    {
      UTEST_CHECK(false);      
    }

  using stester2 = singleton<tester2, int, int>;

  try
    {
      stester2::instance();
      UTEST_CHECK(false);
    }
  catch (std::logic_error& e)
    {
      UTEST_CHECK(true);
    }

  try
    {
      UTEST_CHECK((stester2::create(1, 2).result() == 3));
      UTEST_CHECK((stester2::instance().result() == 3));
    }
  catch (...)
    {
      UTEST_CHECK(false);      
    }
}


来源:https://stackoverflow.com/questions/21395459/how-to-pass-argument-in-a-singleton

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!