0

I have a question about the C++ usage used in the pimpl syntax.

First, why is it not necessary to write pimpl( new impl ) as pimpl( new my_class::impl )

Second, why is the lifetime of new impl extended even though it is a temporary object?

//my_class.h
class my_class {
   //  ... all public and protected stuff goes here ...
private:
   class impl; unique_ptr<impl> pimpl; // opaque type here
}
// my_class.cpp
class my_class::impl {  // defined privately here
  // ... all private data and functions: all of these
  //     can now change without recompiling callers ...
};
my_class::my_class(): pimpl( new impl )
{
  // ... set impl values ...
}
gahhu
  • 31
  • 3
  • Where do you get the idea `new impl` is a temporary? You allocated it from the heap and *just* stored it in a member variable that manages it for the life of the class. – ShadowRanger May 10 '22 at 10:15
  • To 'First', everything in a member of an out of class implementation is available to name resolution once the compiler makes it past `my_class::`, and that includes the leading `my_class::` for the ctor and dtor. Therefore, `my_class::impl` is not longer required; you can use `impl` in that context, including as you are in that member initialization list – WhozCraig May 10 '22 at 10:21
  • You are already in the scope of `my_class` as this is the body of a member function, hence no need to qualify class members. `new impl` is a pointer of type `impl*`. Its lifetime is not extended. It is being used to initialise `pimpl` and then it dies The `impl` that it points at is not a temporary, it is a heap-allocated object. – n. m. could be an AI May 10 '22 at 10:28
  • offtipic: it is not a syntax. It is an idiom or design pattern. – Marek R May 10 '22 at 10:29
  • You are did `using namespace std;` in header (the only case when you can skip `std::` before `unique_ptr`)! This is BIG mistake do not do it. Even having `using namespace std;` in cpp file is considered a bad practice. – Marek R May 10 '22 at 10:40

3 Answers3

2

First: In your constructor the expression new impl indeed is a temporary object, but it is immediately passed to the pimpl member which is not a temporary. The std::unique_ptr type stores the pointer passed to it and holds on to it until it is destroyed or moved. And the member is not destroyed until the my_class object is destroyed.

Also, consider using std::make_unique instead of new.

Second: You don't have to write my_class::impl because you are in a member function of my_class, so name resolution looks not only at namespace scope but also at class scope. So, impl can be resolved.

bitmask
  • 32,434
  • 14
  • 99
  • 159
1

First, why is it not necessary to write pimpl( new impl ) as pimpl( new my_class::impl )

Scope. When constructor is defined you are inside a cope of a class, so name lookup is able to find it without problems.

Second, why is the lifetime of new impl extended even though it is a temporary object?

You are passing temporary pointer to as constructor argument of std::unique_ptr<impl> so value is consumed by a field pimpl. impl value s not temporary since it is created on a heap, control of its lifetime was passed to smart pointer.

Extra:
You did using namespace std; in header (the only case when you can skip std:: before unique_ptr)! This is BIG mistake do not do it. This will impact dangerously all sources which will include this header. You can have symbol ambiguity error because of that.

Even having using namespace std; in cpp file is considered a bad practice.

Marek R
  • 32,568
  • 6
  • 55
  • 140
1

The new impl is not temporary because it's not a variable.

It is an expression, which

  1. is evaluated during your my_class::pimpl object member construction,
  2. its side effect is creation of an impl object (whose lifetime is controlled, not automatic, because it's not a local automatic variable),
  3. and which returns a pointer to the newly created anonymous impl object.

The expression is used as an initializer to the pimpl member of the my_class object being constructed, so the returned pointer is passed as an initializing value to pimpl and gets stored in it. This allows your my_class object to control the life time of the impl object.

The life time of the pimpl pointer variable is obviously the same as its owning my_class object's life time, however the life time of the pointed impl object depends on your my_class logic:

  1. it may keep it until its own end and get it deleted during its own destruction,
  2. it may replace() it with another one at will
  3. or release() the pimpl, delete the impl object and work without any when no longer necessary...
  4. It might even release the object and not delete it, for example to transfer the ownership to another object – or just abandon it, which causes a memory leak (and which is usually a serious error!)
CiaPan
  • 9,381
  • 2
  • 21
  • 35