如何使用C ++单例来返回仅初始化一次的对象

时间:2013-07-25 15:36:14

标签: c++ singleton shared-libraries ffi ruby-ffi

我是一个真正的C ++菜鸟,所以请耐心等待我。首先让我们设置舞台。

我在binary.cpp中有一个C ++源代码,它编译成一个类似于:

的二进制文件
# include "lotsofheaders.h"

int main(int argc, char* argv[])
{
    int errorcode = FOOBAR_GLOBAL_UNKNOWN;

    // foobar instanciation
    Foobar foobar();

    // multiple calls to :send_auth passing in foobar instance
    errorcode = send_auth(getX(), getY(), foobar);
    errorcode = send_auth(getX(), getY(), foobar);
    errorcode = send_auth(getX(), getY(), foobar);

    return errorcode == FOOBAR_OK ? EXIT_SUCCESS : EXIT_FAILURE;
}

从另一个目标代码文件加载send_auth方法,它将传递一个foobar实例。原因是,Foobar来自一个我没有源代码的API对象,并且不能多次实例化。

由于main只被调用一次,所以一切都按预期工作:只有一个Foobar实例,send_auth可以被多次调用。

二进制文件对我来说没用,我需要一个共享对象库来做同样的事情。它只创建一个Foobar实例,并公开一个外部接口方法send_with_auth,可以在加载共享对象lib后多次调用它。

library.cpp中的我的库代码看起来像这样:

# include "lotsofheaders.h"
# include "library.h"

const char* send_with_auth(const char* X, const char* Y){
  std::string x(X);
  std::string y(Y);

  int result = send_auth(x, y, 'some Foobar singleton');

  return true;
}

由于我通过Ruby FFI加载我的共享对象库,我需要在library.h中为我的lib添加一些C-Style头:

extern "C" {
  const char* send_with_auth(const char* X, const char* Y);
}

现在使用舞台设置我必须在我的库中创建一个Foobar实例,并将其传递给send_auth的每次调用,以免从Foobar中获得内存冲突错误。

根据我的理解,这是我的(过于复杂)尝试使用单身人士。有一个新的library.h就像这样:

extern "C" {
  class Singleton
  {
    private:
      static bool instanceFlag;
      static Singleton *single;
      Singleton()
      {
        //private constructor
      }
    public:
      static Foobar* fo;
      static Singleton* getInstance();
      ~Singleton()
      {
        instanceFlag = false;
      }
    };
  const char* send_with_auth(const char* X, const char* Y);
}

此实施library.cpp

# include "lotsofheaders.h"
# include "library.h"

bool Singleton::instanceFlag = false;
Singleton* Singleton::single = NULL;
Singleton* Singleton::getInstance()
{
  if(! instanceFlag)
  {
    single = new Singleton();
    instanceFlag = true;
    // bringing up my Foobar instance once and only once
    Foobar fo;
    single->fo = &fo;
    return single;
  }
  else
  {
    return single;
  }
}

const char* send_with_auth(const char* X, const char* Y){
  std::string x(X);
  std::string y(Y);

  Singleton *single;
  single = Singleton::getInstance();

  int result = send_auth(x, y, *single->fo);

  return true;
}

此代码至少编译,我可以将所有内容绑定到共享对象库。现在当我在外部进程(在我的情况下使用Ruby FFI的Ruby模块)中加载该库时,我总是得到错误:

Could not open library '/some/path/libfoobar.so': /some/path/libfoobar.so: undefined symbol: _ZN9Singleton2erE (LoadError)

我很确定我从library.o到libfoobar.so的编译/绑定/剥离过程没问题,因为在其他情况下它会成功。我很确定我在这里误解了C ++的单例概念。我想知道如何实现我的目标:在我的共享对象库中只创建一个Foobar实例,并将其传递给我的库暴露给外部的唯一方法的每次调用。

有人可以提供帮助吗? 问菲利克斯

更新

在库中使用CommandlineParser是愚蠢的。实际上它只返回了两个C字符串。 API库路径和日志目录。我用它重新创建了一个命名空间:

namespace
{
  char* home("/path/to/api/libs");
  char* log("/tmp");
  Foobar foobar(home, log);
}

当我加载库时,这会导致seg错误。与此相反,我可以直接将这些行放入我的函数中:

const char* send_with_auth(const char* X, const char* Y){
  std::string x(X);
  std::string y(Y);

  char* home("/path/to/api/libs");
  char* log("/tmp");
  Foobar foobar(home, log); 

  int result = send_auth(x, y, &foobar);

  return true;
}

除了事实上第二次调用send_with_auth让崩溃导致Foobar再次被实例化之外,其他一切都运行正常。

更新2:

最后我解决它更简单,使用全局可用的bool开关只初始化我的Foobar实例一次:

namespace baz{
  bool foobarInitialized = false;
}

const char* send_with_auth(const char* certificatePath, const char* certificatePin, const char* xmlData){
  std::string certificate_path(certificatePath);
  std::string certificate_pin(certificatePin);
  std::string xml_data(xmlData);

  Foobar *foobar_ptr;

  if (! baz::foobarInitialized) {
    char* home_dir("/path/to/api/lib");
    char* log_dir("/tmp");
    foobar_ptr = new Foobar(home_dir, log_dir);
    baz::foobarInitialized = true;
  }

  int result = send_auth(x, y, foobar_ptr);

  return xml_data.c_str();
}

现在,我可以无休止地致电send_with_auth,而不必多次实例化Foobar。完成!

1 个答案:

答案 0 :(得分:2)

所以我的第一个建议就是

可能最简单的事情

完全摆脱单身人士。只需在库中创建一个全局Foobar对象:

// library.cpp
namespace {
    Foobar global_foobar;
}

我已将它放在一个匿名命名空间中,因此它不会与主可执行文件或其他库中名为“global_foobar”的任何其他符号冲突。

这意味着它只能从library.cpp中访问。如果您的lib中链接了多个.cpp文件,那么您需要更精细的内容:

// library_internal.h
namespace my_unique_library_namespace {
    extern Foobar global_foobar;
}

// library.cpp
#include "library_internal.h"
namespace my_unique_library_namespace {
    Foobar global_foobar;
}

// library_2.cpp
#include "library_internal.h"
using my_unique_library_namespace::global_foobar;