【问题标题】:make_unique doesn't compile for creating a singleton instancemake_unique 不能为创建单例实例而编译
【发布时间】:2018-06-22 21:06:29
【问题描述】:

全部,

我正在使用 C++14,并且正在制作或多或少的标准 Singleton。我正在使用最新的 Visual Studio 2017。此代码有效:

#include <memory>
class A
{
public:
  static A& getInstance()
  {
    if (instance == nullptr)
      instance = std::unique_ptr<A>(new A());
    return *instance;
  }

private:
  A() {}
  static std::unique_ptr<A> instance;
};
std::unique_ptr<A> A::instance = nullptr;

但是,当我将单例实例的创建更改为:

instance = std::make_unique<A>();

我在尝试访问私有成员时遇到编译错误:

Error   C2248   'A::A': cannot access private member declared in class 'A'      
c:\program files (x86)\microsoft visual studio\2017\professional\vc\tools\msvc\14.14.26428\include\memory   2510    

这对我来说就像一个错误,因为这两种形式在功能上应该是相同的?想法?

【问题讨论】:

  • 类似于stackoverflow.com/q/33905030/241631make_unique 和其他函数一样,为什么它可以调用你类的私有成员?
  • 无论如何,你可以通过将函数定义为static A&amp; getInstance() { static A instance; return instance; }来避免这一切
  • 两种解决方案都存在竞争条件和重入问题。也就是说,单身人士通常被认为是一个坏主意,所以无论如何我都会避免这样做。
  • @UlrichEckhardt Praetorian 解决方案中的竞争条件是什么? Meyers Singleton 从 C++11 开始是线程安全的,并且 OP 已经表明他们正在使用 C++14。
  • @DGehlhaar 正如 Nevin 所说,我发布的内容在 C++11 之后已经是线程安全的。而且我仍然没有看到动态分配将解决的静态初始化顺序问题与函数本地静态相比(仅在您第一次调用getInstance时进行初始化)

标签: c++ singleton c++14 unique-ptr private-constructor


【解决方案1】:

std::unique_ptr&lt;&gt; 的目的是控制所指向对象的生命周期。您可以传递std::unique_ptr&lt;&gt;,但这也会转移指向对象的所有权。这个概念与单例的概念不太匹配。只有一个地方可以创建(或删除)单例。你真的不需要std::unique_ptr&lt;&gt;。正如在 cmets 中已经说过的,有更简单的方法。我更喜欢@Praetorian 的建议:

static A& getInstance() 
{
     static A instance;
     return instance;
}

你不能使用std::make_unique&lt;&gt;() 来实例化你的类的原因是构造函数是私有的。要访问它,您需要访问函数为friend。看看How to make std::make_unique a friend of my class。另一种解决方案是提供一个需要私有类型作为参数的公共构造函数,如 int this solution 所述。

【讨论】:

  • 在哪里指定你应该让make_unique成为朋友?
  • class A 的构造函数是私有的。 std::make_unique&lt;&gt;() 需要访问该构造函数。这就是 OP 出现编译错误的原因。因此,if OP 仍然希望将 std::make_unique&lt;&gt;() 用于单例(我和上面的许多评论者都不推荐),然后将 std::make_unique&lt;&gt; 设为类的朋友来实例化将解决问题。
  • "std::make_unique() 需要访问" 是的我看到了。是否记录在案?在哪里保证是这种情况?你知道,某种“合同”。
  • @curiousguy ISO/IEC 14482 2014 通道。 20.8.1.4 [unique.ptr.create]:返回unique_ptr&lt;T&gt;(new T(std::forward&lt;Args&gt;(args)...))。显然,要创建T 类型的对象,您需要访问构造函数。您可能还想看看en.cppreference.com/w/cpp/memory/unique_ptr/make_unique
  • 有没有提到朋友?
【解决方案2】:
instance = std::make_unique<A>();

这会在函数make_unique 中创建A。但是您要调用的 ctor 是私有的。

private:
  struct ctor_permission_t{
    explicit ctor_permission_t(int){};
  };
public:
  explicit A(ctor_permission_t):A(){}
};

然后

instance = std::make_unique<A>(ctor_permission_t{0});

ctor_permission_t 充当令牌,赋予其拥有者构建A 的权利。我们将其传递给make_unique

ctor_permission_t 中的 explicit int ctor 使得如果不命名类型就无法创建它,并且只有在 A 的实例和朋友中才能命名它,因为它是私有的。这使得绕过此权限令牌变得困难。

【讨论】:

  • 这是一个非常酷的解决方案。但是,ctor_permission_t 的 ctor 需要一个参数。所以,你需要写instance = std::make_unique&lt;A&gt;(ctor_permission_t{0});A 的 ctor 中还有一个错字('_t' 缺失)。恕我直言,该解决方案之所以有效,是因为 ctor_permission_t 是私有的,而不是因为 explicit int。如果与struct ctor_permission_t{} 配合得很好。然后你可以调用instance = std::make_unique&lt;A&gt;(ctor_permission_t());,但仍然无法实例化A而不访问私有类型ctor_permission_t
  • @adr {} 可以在没有 int 参数的情况下构造它。因此,如果缺少显式 int ctor,则有人可以 A({}),而无需命名 ctor 权限类型。
  • 不同意。如果我删除了ctor_permission_t 的显式ctor,我仍然无法调用A({}),因为整个struct ctor_permission_t 是私有的,我无法从外部访问它。测试它(使用 gcc 6.3),我得到 error: ‘struct A::ctor_permission_t’ is private in this context 正如预期的那样。
  • @adrian live example。也在最新的clang中编译。您的编译器有错误或您的示例不同。因为struct A{ private: struct B{}; public: A(B){}};允许非好友在标准下A a({});。如果您想了解更多信息,请使用 [ask question] 链接并发布有关此问题的新 SO 问题; cmets 是语言细节问答的好地方。
  • 您的示例缺少 A 的私有默认 ctor 以与 OP 的情况相媲美。如果您将private: A() = default; 添加到您的示例中,它将不再编译。
【解决方案3】:

总结其他答案并修复其中的一些缺陷: 您可以使用私有构造函数创建私有结构,该构造函数是您的类的朋友。然后将您的类构造函数公开,但带有该私有结构的附加参数。

另外最好使用静态函数,它返回一个引用而不是裸静态变量。

#include <memory>

class A
{
  struct Private
  { 
    friend A;
    private:
      explicit Private() = default; 
  };

public:
  static A * getInstance()
  {
    if (!instance())
      instance() = std::make_unique<A>(Private());

    return instance();
  }

  A(Private) {};

private:
  static std::unique_ptr<A> & instance()
  {
    static std::unique_ptr<A> inst;
    return inst;
  }
};

或者,如果您真的不需要任何需要使用指针和堆分配的特殊配置(例如在特殊线程中初始化实例或...):

class A
{
public:
  static A & getInstance()
  {
    static A instance;
    return instance;
  }

private:
  A() = default;
};

【讨论】:

    猜你喜欢
    • 2012-12-02
    • 1970-01-01
    • 1970-01-01
    • 2014-06-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多