Let's use a simple class as T type of important_struct:
class Data
{
public:
Data() : something(0){}
Data(int i) : something(i){}
Data(const Data & d) : something(d.something){}
//non-const method: something can be modified
void changeSomething(int s){ something += s; }
//const method: something is read-only
int readSomething() const { return something; }
private:
int something;
};
This class has a very simple, yet well encapsulated, status, i.e. the int something field, which is accessed through methods in a very controlled way.
Let (a simplified version of) important_structure hold an instance of Data as a private field:
template <typename T>
struct important_structure
{
public:
important_structure(T * el);
void change();
int read() const;
private:
T* data;
};
We can assign a Data instance to an important_structure instance this way:
important_structure<Data> s(new Data());
The instance is assigned in construction:
template <typename T>
important_structure <T>::important_structure(T * el) : data(el) {}
Now the great question: do important_structure take ownership of the Data instances it holds? The answer must be made clear in documentation.
If it is yes, important_structure must take care of memory cleanup, e.g. a destructor like this one is required:
template<typename T>
important_structure<T>::~important_structure()
{
delete data;
}
Notice that, in this case:
Data * p = new Data()
// ...
important_structure<Data> s(p);
//p is left around ...
another pointer to the Data istance is left around. What if someone mistakenly call delete on it? Or, even worse:
Data d;
// ...
important_structure<Data> s(&p); //ouch
A much better design would let important_structure own its own Data instance :
template <typename T>
struct important_structure
{
public:
important_structure();
void change();
// etc ...
private:
T data; //the instance
};
but this is maybe simplistic or just unwanted.
One could let important_structure copy the instance it will own:
template<typename T>
important_structure<T>::important_structure(const T &el)
{
data = el;
}
the latter being the constructor provided in the question: the object passed won't be touched, but copied. Obviously, there are two identical Data objects around, now. Again, the result could not be what we needed in the first place.
There is a third way, in the middle: the object is instantiated outside the owner, and moved to it, using move semantics.
As an example, let's give Data a move assignment operator:
Data & operator=(Data && d)
{
this->something = d.something;
d.something = 0;
return *this;
}
and let important_structure provide a constructor which accepts an rvalue reference of T:
important_structure(T && el)
{
data = std::move(el);
}
One can still pass a Data instance using a temporary as the required rvalue:
important_structure<Data> s(Data(42));
or an existing one, providing the required reference from an lvalue, thanks to std::move:
Data d(42);
// ...
important_structure<Data> x(std::move(d));
std::cout << "X: " << x.read() << std::endl;
std::cout << "D: " << d.readSomething() << std::endl;
In this second example, the copy held by important_structure is considered the good one while the other is left in a valid but unspecified state, just to follow the standard library habits.
This pattern is, IMHO, more clearly stated right in code, expecially if considered that this code will not compile:
Data d(42);
important_structure<Data> x (d);
Whoever wants an instance of important_structure must provide a temporary Data instance or explicitly move an existing one with std::move.
Now, let the important_structure class be a container, as you asked in comment, so that data is somehow accessible from outside. Let's give a method like this to the important_structure class:
const T & owneddata() { return data; }
Now, we can use data const methods like this:
important_structure<Data> s(Data(42));
std::cout << s.owneddata().readSomething() << std::endl;
but calls to `Data' non-const methods will not compile:
s.owneddata().changeSomething(1000); //not compiling ...
If in need of it (hope not), expose a non-const reference:
T & writablereference() { return data; }
Now the data field is at full disposal:
s.writablereference().changeSomething(1000); //non-const method called
std::cout << s.owneddata().readSomething() << std::endl;