Home > Mobile >  Binding temporary to r-value reference produces error
Binding temporary to r-value reference produces error

Time:03-15

I'm trying to write a pImpl without using a unique_ptr. I don't understand while writing something like this:

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

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

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

produces following compilation error

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

while writing the following

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

is ok. R-value references should not extend life-time of temporaries, as in first snippet?

CodePudding user response:

From 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.

This is applicable to both of your examples. That is, in both of your given cases you have a dangling reference. Its just that in case 2 of your example the compiler is not able to give us the appropriate error/warning.

CodePudding user response:

First of all you must understand that every object requires the storage. You have 3 storages:

  • Stack
  • Heap
  • Global namespace (sorry, I've forgotten how do they call it)

Both PublicClass and PublicClass::ImplClass are classes and to create an instance of this class you need the storage.

So you first decide where do you want to allocate the ImplClass.

In case if you want to be able allocate both PublicClass and PublicClass::ImplClass on the stack, compiler must know the size of the ImplClass at compile time. 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. What you can do is to pre-allocate the memory using char[N] variable

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

In case if you do not care where ImplClass is allocated, you can allocate it on the heap. In this case you use new and delete operators and you need a RAII-class to manage the resource.

unique_ptr is an example of the RAII-class. If for any reason you do not want to use it, you must implement the RAII inside the PublicClass. I.e. you implement constructor which allocates the ClassImpl on the heap and you implement the destructor, which releases the resources. You also have to care about move/copy constructors and move/assignment operators, as default behaviour provided by C language does not work right here.

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

In case if by design you have only one instance of the object, you can allocate the ClassImpl in the global namespace. Personally, I do not like this solution.

  • Related