Lets say with have generic code like the following:
y.hpp:
#ifndef Y_HPP
#define Y_HPP
// LOTS OF FILES INCLUDED
template <class T>
class Y
{
public:
T z;
// LOTS OF STUFF HERE
};
#endif
Now, we want to be able to use a Y in a class (say X) we create. However, we don't want users of X to have to include the Y headers.
So we define a class X, something like this:
x.hpp:
#ifndef X_HPP
#define X_HPP
template <class T>
class Y;
class X
{
public:
~X();
void some_method(int blah);
private:
Y<int>* y_;
};
#endif
Note that, because y_ is a pointer, we don't need to include its implementation.
The implementation is in x.cpp, which is separately compiled:
x.cpp:
#include "x.hpp"
#include "y.hpp"
X::~X() { delete y_; }
void X::someMethod(int blah) { y_->z = blah; }
So now our clients can just include "x.hpp" to use X, without including and having to process all of "y.hpp" headers:
main.cpp:
#include "x.hpp"
int main()
{
X x;
x.blah(42);
return 0;
}
And now we can compile main.cpp
and x.cpp
separately, and when compiling main.cpp
I don't need to include y.hpp
.
However with this code I've had to use a raw pointer, and furthermore, I've had to use a delete.
So here are my questions:
(1) Is there a way I could make Y a direct member (not a pointer to Y) of X, without needing to include the Y headers? (I strongly suspect the answer to this question is no)
(2) Is there a way I could use a smart pointer class to handle the heap allocated Y? unique_ptr
seems like the obvious choice, but when I change the line in x.hpp
from:
Y<int>* y_;
to:
std::unique_ptr< Y<int> > y_;
and include , and compile with c++0x mode, I get the error:
/usr/include/c++/4.4/bits/unique_ptr.h:64: error: invalid application of ‘sizeof’ to incomplete type ‘Y<int>’
/usr/include/c++/4.4/bits/unique_ptr.h:62: error: static assertion failed: "can't delete pointer to incomplete type"
so is there anyway to do this by using a standard smart pointer instead of a raw pointer and also a raw delete in a custom destructor?
Solution:
Howard Hinnant has got it right, all we need to do is change x.hpp
and x.cpp
in the following fashion:
x.hpp:
#ifndef X_HPP
#define X_HPP
#include <memory>
template <class T>
class Y;
class X
{
public:
X(); // ADD CONSTRUCTOR FOR X();
~X();
void some_method(int blah);
private:
std::unique_ptr< Y<int> > y_;
};
#endif
x.cpp:
#include "x.hpp"
#include "y.hpp"
X::X() : y_(new Y<int>()) {} // ADD CONSTRUCTOR FOR X();
X::~X() {}
void X::someMethod(int blah) { y_->z = blah; }
And we're good to use unique_ptr. Thanks Howard!
Rationale behind solution:
People can correct me if I'm wrong, but the issue with this code was that the implicit default constructor was trying to default initialize Y, and because it doesn't know anything about Y, it can't do that. By explicitly saying we will define a constructor elsewhere, the compiler thinks "well, I don't have to worry about constructing Y, because it's compiled elsewhere".
Really, I should have added a constructor in the first place, my program is buggy without it.