如何在单例中传递参数

Posted

技术标签:

【中文标题】如何在单例中传递参数【英文标题】:How to pass argument in a singleton 【发布时间】:2014-02-19 03:40:49 【问题描述】:

我一直想知道如何将参数传递给单例构造函数。我已经知道如何做单例了,但是我很不幸地找到了一种方法。

这是我的代码(部分)。

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;

在此先感谢

(顺便说一句,我知道如何以及为什么单身不好)

【问题讨论】:

既然你已经有了一个单例,创建几个全局变量并没有什么坏处,每个构造函数参数一个,然后在第一次调用getInstance()之前为所有这些变量赋值。并且不需要new单例实例,只要在getInstance()内部做一个静态变量,C++11甚至保证它的初始化是线程安全的。 【参考方案1】:

您不需要动态分配单例的实例。它可能看起来像以下方式(这有时被称为“延迟加载单例”~实例创建较晚并且有点“自动”):

#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");

输出:

My name is: Josh
My name is: Josh

请注意,当第一次调用getInstance 时,构造函数只会被调用一次。

【讨论】:

不动态分配单例是正确的想法,但如果你要直接分配数据成员,那么有构造函数参数有什么意义? @Praetorian:这是为了指出传递的字符串也可能用于在下一次 getInstance 调用中更改该单例的状态,但你是对的,它使构造函数在这里没用。 谢谢,所以如果我做对了,我只需要传递我的参数就是将它们放在 getInstance 函数中,然后将它传递给我的构造函数。 如何向此实现添加不带参数的 getInstance 方法? (只是为了稍后获取实例,不需要传递参数) 我不明白为什么这是公认的答案,因为它展示了如何将参数传递给单例的构造函数。此外,要求传递参数,即使除了第一次调用之外的所有参数都会被忽略,这是造成混乱的主要来源【参考方案2】:

让我为您的用例扩展Martin York's answer。我建议在这种特殊情况下使用指针作为参数,因为我们利用它的固有属性,它可以是“空的”。

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;
;

我发现这种方法不那么令人困惑,因为它让您在第一次初始化后获得实例而无需(无论如何丢弃)参数:

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

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

编辑:从 getInstance 函数中删除了参数并进行了一些优化。

【讨论】:

它比接受的答案更好,因为您不必传递参数,但我仍然不喜欢您可以传递参数(如果实例已经存在,则会被忽略)。 @user463035818 我编辑了代码,以免在getInstance 函数中使用未使用的参数分散用户的注意力。你可以接受这种形状吗? 不错的解决方案,但我认为您的编辑应该是Questionnary::init(s)【参考方案3】:

有一个方法来创建实例以将参数传递给构造函数,如果在调用它之前没有调用 CreateInstance,您可以在 getInstance() 方法中断言。喜欢:

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_;
;

【讨论】:

很好,但是如果构造过程需要创建本身引用 getInstance() 的对象,它会变得混乱......但我想我们不应该这样做 ;-)同时,我使用bool 进行断言,并在调用new 之前设置此,以便assert期间 构造成功。 (为了让这一切变得更加丑陋,我使用了位置new...我是个怪物)【参考方案4】:

我的版本使用现代 C++,包装现有类型:

#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

这是单元测试中的样子:

  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);

问题在于 instance() 在每次调用时都需要参数,但只在第一次调用时使用它们(如上所述)。一般不能使用默认参数。最好使用 create(Args...) 方法,该方法必须在调用 instance() 或引发异常之前。

【讨论】:

有趣。我也喜欢你如何设法在你的答案中提到单元测试。这是一个经常被遗忘的方面。 也许我在这里很无知,但是如何从静态方法中访问成员变量呢?这是更现代的 C++ 标准的一个特性吗? 我没有访问成员变量。它被贴错标签。我应该将其标记为 s_instance【参考方案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

和:

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);      
    

【讨论】:

以上是关于如何在单例中传递参数的主要内容,如果未能解决你的问题,请参考以下文章

在单例中使用 BroadcastReceiver 的最巧妙方法等等

如何检查 googlemock 中作为 void 指针传递的字符串参数

如何在 Robot Framework 测试用例的命令行参数中传递 Tab?

如何在两个不同的view 之间进行参数传递?

多视图参数传递

传递一个 Binding 和一个常量字符串作为参数