【问题标题】: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甚至保证它的初始化是线程安全的。

标签: c++ singleton argument-passing


【解决方案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);      
    }
}

【讨论】:

    猜你喜欢
    • 2019-03-12
    • 2021-10-31
    • 2018-02-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多