I'm playing with a couple of concepts that I am still rather new to. What I am trying to do is dependency inject a "Screen" object into the private member "TempCtrl::mScreen" using unique pointers. I believe I am implementing the design pattern correctly, but I have never done this with unique pointers and it would seem that the pointer is deleted prior to the TempCtrl constructor call. Why is this happening?
excerpt of Main Function:
#include "tempctrl.hpp"
#include "screen.hpp"
#include <memory>
int main()
{
std::unique_ptr<Screen> _Screen(new Screen);
TempCtrl tc(_Screen);
/* ... */
}
excerpt of TempCtrl constructor declaration:
class TempCtrl
{
public:
TempCtrl(std::unique_ptr<Screen> _Screen);
~TempCtrl();
private:
std::unique_ptr<Screen> mScreen;
};
excerpt of TempCtrl implementaton:
TempCtrl::TempCtrl(std::unique_ptr<Screen> _Screen)
: mScreen(_Screen)
{
}
compiler output:
/usr/bin/g++ -std=c++11 -g -c screen.cpp tempctrl.cpp main.cpp
tempctrl.cpp: In constructor 'TempCtrl::TempCtrl(std::unique_ptr<Screen>)':
tempctrl.cpp:6:18: error: use of deleted function 'std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = Screen; _Dp = std::default_delete<Screen>]'
: mScreen(_Screen)
^
In file included from /usr/include/c++/8/memory:80,
from tempctrl.hpp:14,
from tempctrl.cpp:1:
/usr/include/c++/8/bits/unique_ptr.h:394:7: note: declared here
unique_ptr(const unique_ptr&) = delete;
^~~~~~~~~~
main.cpp: In function 'int main()':
main.cpp:9:22: error: use of deleted function 'std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = Screen; _Dp = std::default_delete<Screen>]'
TempCtrl tc(_Screen);
^
In file included from /usr/include/c++/8/memory:80,
from tempctrl.hpp:14,
from main.cpp:1:
/usr/include/c++/8/bits/unique_ptr.h:394:7: note: declared here
unique_ptr(const unique_ptr&) = delete;
^~~~~~~~~~
In file included from main.cpp:1:
tempctrl.hpp:68:3: note: initializing argument 1 of 'TempCtrl::TempCtrl(std::unique_ptr<Screen>)'
TempCtrl(std::unique_ptr<Screen> _Screen);
^~~~~~~~
make: *** [Makefile:14: *.o] Error 1
Using a deleted function doesn't mean something is deleting itself.
A deleted function in C++ is a function which, when the C++ compiler thinks it should call it, it generates an error on purpose.
Here, your deleted function is your unique_ptr
’s "copy constructor".
A copy constructor is how you take an object in C++ and make another copy of it.
unique_ptr
is supposed to be unique. Copying something unique is against the rules.
Your attempt to call that constructor ... attempted to copy the unique_ptr
.
std::unique_ptr<Screen> _Screen(new Screen);
TempCtrl tc(_Screen); // <— here
Here you have the unique pointer _Screen
1. It uniquely and only owns the new Screen
resource.
You then create a TempCtrl
. The constructor of TempCtrl tc
takes a unique_ptr<Screen>
by value -- this is another unique pointer.
So when you call that constructor, the C++ compiler tries to copy the unique_ptr
, and spews errors at you that mean "you aren't allowed to do that".
Either you have to move (transfer ownership) of your unique_ptr<Screen>
into the TempCtrl
:
TempCtrl tc(std::move(_Screen));
Or, you have to pass the unique_ptr
by reference:
TempCtrl(std::unique_ptr<Screen> const & _screen);
Or, just pass the raw pointer:
TempCtrl(Screen* _screen);
These will in turn require changing other code to make it work. Storing a unique pointer to screen inside a control means that the control, and no other control, owns that screen. This seems strange.
unique_ptr
means that one and only one smart pointer has sole and complete ownership over the lifetime of that pointed to object. Having two unique pointers with the same object is thus nonsense.
It seems likely you are used to garbage-collected languages. In C++, you are responsible for the lifetime of every object you interact with. Smart pointers can help with this, but there is no smart pointer in C++ that will let you not care about object lifetime (blindly and without thought, using a shared pointer for this purpose just makes inevidible the object lifetime bugs harder to find).
This is going to be an extra cognitive load on you, something you aren't used to thinking about. There are techniques in C++ that reduce this load, but learning them isn't trivial.
This may be one of the circumstances where a shared_ptr
or a weak_ptr
is right, but I'm uncertain. The screen staying alive as long as any control stays alive is strange, and a control outlasting the screen it on is also strange.
I suspect controls should exist in layouts, and only when drawing would they get the screen from the layout. The layout would manage the lifetime of the controls. And the drawing functionality would exist outside of the layout, and call into the layout with a screen to draw on.
Thinking which code should be in charge, own and manage which other code is work. Good luck.
1 By the way, that name _Screen
is reserved by the C++ implementation; it is illegal for you to use it. Don't start your identifiers with an _
followed by a capital letter, that makes your program ill-formed, but no diagnostic is required). People copy system headers, which are allowed to use it as they are written by the compiler vendor, and make ill-formed programs this way.