临时绑定到 r 值引用会产生错误

Binding temporary to r-value reference produces error

我试图在不使用 unique_ptr 的情况下编写 pImpl。我在写这样的东西时不明白:

class PublicClass
{
public:
    // Some stuff
    PublicClass();
private:
    class ImplClass;
    ImplClass&& mImpl;
};

class PublicClass::ImplClass
{
public:
    ImplClass() {}
};

PublicClass::PublicClass() : mImpl(ImplClass()){}

产生以下编译错误

Reference member 'mImpl' binds to a temporary object whose lifetime would be shorter than the lifetime of the constructed object

一边写一边

PublicClass::PublicClass() : mImpl(std::move(ImplClass())){}

没问题。 R 值引用不应延长临时对象的生命周期,如第一个片段?

来自class.temporary

The second context is when a reference is bound to a temporary. The temporary to which the reference is bound or the temporary that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference except:

  • A temporary bound to a reference member in a constructor's ctor-initializer ([class.base.init]) persists until the constructor exits.

这适用于您的两个示例。也就是说,在您给定的两种情况下,您都有一个 悬空引用。 只是在您示例的情况 2 中,编译器无法为我们提供适当的 error/warning。

首先你必须明白每个对象都需要存储。您有 3 个存储空间:

  • 堆栈
  • 静态存储(定义全局变量的地方)

PublicClassPublicClass::ImplClass 都是 class 并且要创建此 class 的实例,您需要存储空间。

所以你首先决定你想把ImplClass分配到哪里。

如果您希望能够在堆栈上分配 PublicClassPublicClass::ImplClass,编译器必须在 编译时知道 ImplClass 的大小。我的意思是,如果在创建对象的编译时不知道对象的大小,则不能在堆栈上分配对象。你可以做的是 pre-allocate 使用 char[N] 变量

的内存
class PublicClass
{
  // must be large enough to fit the ImplClass
  static constexpr auto PublicClassImplSize = 128; 

  // The storage for ClassImpl
  char alignas(void*) impl_[PublicClassImplSize];

  class ImplClass;

public:
  PublicClass();
  ~PublicClass();
};

// cpp
#include <new>

class PublicClass::ImplClass
{
    char buf1[10];
//    char buf2[10000];
};

PublicClass::PublicClass()
{
  static_assert(sizeof(ImplClass) <= PublicClassImplSize);
  new (impl_) ImplClass();

}

PublicClass::~PublicClass()
{
  reinterpret_cast<ImplClass*>(impl_)->~ImplClass();
}


int main()
{
    PublicClass o;
}

如果你不关心ImplClass分配在哪里,你可以在堆上分配它。在这种情况下,您使用 new/delete 运算符来 allocate/release 内存并在 PublicClass 中实现 RAII 来管理资源。

unique_ptr是RAII-class的例子。如果出于任何原因您不想使用它,则必须在 PublicClass 内实现 RAII。 IE。您实现了在堆上分配 ClassImpl 的构造函数,并实现了释放资源的析构函数。您还必须关心 move/copy 构造函数和 move/assignment 运算符,因为 C++ 语言提供的默认行为在这里不起作用。

class PublicClass
{
  class ImplClass;
  ImplClass* impl_{nullptr};

public:
  PublicClass();
  ~PublicClass();

  PublicClass(PublicClass&&) noexcept;
  PublicClass& operator=(PublicClass&&) noexcept;
};

// cpp
#include <new>
#include <memory>

class PublicClass::ImplClass
{
    char buf1[10];
//    char buf2[10000];
};

PublicClass::PublicClass()
{
  auto impl = std::make_unique<ImplClass>();

  // ... more initialization

  // Initialization is completed
  impl_ = impl.release();
}

PublicClass::PublicClass(PublicClass&& obj) noexcept
  : impl_(std::exchange(obj.impl_, nullptr))
{
}

PublicClass& PublicClass::operator=(PublicClass&& obj) noexcept
{
  delete impl_;
  impl_ = std::exchange(obj.impl_, nullptr);

  return *this;
}

PublicClass::~PublicClass()
{
  delete impl_;
}


int main()
{
    PublicClass o;
}

如果根据设计您只有一个对象实例,则可以在全局命名空间中分配 ClassImpl。就个人而言,我不喜欢这个解决方案。

更新

I mean you cannot allocate the object on the stack if the size of the object is not known at compile time at the point where object is created

alloca 函数的使用超出范围:)