Reputation: 6297
Yesterday I asked the following question, reproduced here for convenience;
"For one of my projects, what I really wanted to do was this (simplifying it to the bare minimum);
struct Move
{
int src;
int dst;
};
struct MoveTree
{
Move move;
std::vector<MoveTree> variation;
};
I must admit that I assumed that it wouldn't be possible to do this directly, I thought a vector of MoveTree s within a MoveTree would be verboten. But I tried it anyway, and it works beautifully. I am using Microsoft Visual Studio 2010 Express.
Is this portable ? Is it good practice ? Do I have anything to worry about ?"
Basically the answer from the community was no, I couldn't do this, the standard forbids it, so the fact that it works means I am just getting lucky.
So my new question is. How can I implement the simple functionality I want in legal C++, without adding a whole heap of nasty complexity and pain ?
Upvotes: 2
Views: 1098
Reputation: 1424
Use a pointer (or smart pointer) to the type in the Vector, this will be portable. This is because a pointer to a type is complete by just it declaration you don't need the definition.
struct Move
{
int src;
int dst;
};
struct MoveTree;
struct MoveTree
{
Move move;
std::vector<MoveTree*> variation;
};
If you require the type to be managed then use a smart pointer that can handle the delete for you.
Original answer to this question.
Upvotes: 0
Reputation: 59111
If you can use the incomplete type MoveTree
as a template parameter in any template, then use one of the solutions in the other answers (e.g. Cat Plus Plus's), or simply use your original solution, add some heavy comments, and do your penance later.
If you can't use it as any template parameter while it is still incomplete, you could use the pimpl idiom to work around this.
By the time the implementation class is defined, the MoveTree
class will be complete:
struct Move
{
int src;
int dst;
};
struct MoveTreeImpl;
struct MoveTree
{
Move move;
// Todo: Implement RAII here for the impl
// Todo: Provide impl accessor functions here
private:
MoveTreeImpl* impl;
};
struct MoveTreeImpl
{
std::vector<MoveTree> variation;
};
There are hairy parts to this solution:
Since you're trying to avoid direct instantiation of any template with incomplete types, you'll have to implement RAII manually. You won't get help from std::scoped_ptr<MoveTreeImpl>
, as MoveTreeImpl
is also incomplete.
What signature do your accessor functions have? Can you return a std::vector<MoveTree>&
from an accessor? I'm not sure on this - we're trying to avoid templates that use MoveTree
directly. This might be different, because it is not a data member, but I'm not sure :)
Edit:
Reading a bit more, and getting responses from other users, it seems much of this nonsense is unnecessary. It is only the standard containers that have the restriction. You could implement the pimpl
with a std:scoped_ptr<MoveTreeImpl>
, and I think return std::vector<MoveTree>&
from an accessor function, both with no problems.
Upvotes: 1
Reputation: 4905
You may use Boost Pointer Container Library. It similar to using std::vector, but the container owns the pointer, so it will destroy the objects.
You may declare it:
#include <boost/ptr_container/ptr_vector.hpp>
struct MoveTree{
Move move;
boost::ptr_vector<MoveTree> variation;
};
Now, if you want to add a new element, you may use:
variation.push_back(new MoveTree());
Now, the container owns the pointer and you get it by reference, for example:
variation[i].move.src = ...
The object is destroyed when the container is destroyed.
Upvotes: 3
Reputation: 231113
I don't have a copy of the C++ standard on-hand, so I can't check the standard, but here's the problem:
struct foo { foo x; }
is illegal, for obvious reasons. More generally, you cannot reference the size of the structure in any of its members, except the body of member functions.struct foo { foo *x; }
is perfectly fineSo the question is, does std::vector
define any members (other than member functions) which directly or indirectly depend on sizeof(MoveTree)
?
Obviously, std::vector
cannot have a static member that is MoveTree
itself, as this would imply that an empty vector invokes the MoveTree constructor. However one could envision a std::vector with an aligned char inlineStorage[sizeof(MoveTree)]
optimization for one-element vectors. Leaving aside whether this would improve performance, the question at hand is if the standard allows the implementation to take this sort of approach.
That said, it's still a bad idea for a different reason: Because vectors have to copy their elements when resizing their storage, it's a bad idea to have elements with an expensive copy constructor in a vector. Here we have a class whose copy constructor has to recursively recreate the entire tree. It would be better to use a smart pointer class to indirectly reference the child nodes to avoid this overhead:
std::vector<boost::shared_ptr<MoveTree> > variation;
// in C++0x:
std::vector<std::shared_ptr<MoveTree> > variation;
// in C++0x you can also use for lower overhead:
std::vector<std::unique_ptr<MoveTree> > variation;
// you must then use this pattern to push:
variation.push_back(std::move(std::unique_ptr<MoveTree>(new MoveTree())));
Upvotes: 1
Reputation: 129774
You'll need to use pointers, and dynamic allocation. And you should use smart pointers, to ensure you don't leak anything. boost::shared_ptr
allows the type to be incomplete, and therefore this is legal:
std::vector< boost::shared_ptr<MoveTree> > variation;
(I don't know about 0x std::shared_ptr
TBH, but it should be the same).
Upvotes: 6