Bill Forster
Bill Forster

Reputation: 6297

How can I emulate a recursive type definition in C++?

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

Answers (5)

David Allan Finch
David Allan Finch

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

Merlyn Morgan-Graham
Merlyn Morgan-Graham

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

J. Calleja
J. Calleja

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

bdonlan
bdonlan

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:

  • Classes cannot contain concrete instances of themselves, even transitively - eg, 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.
  • Classes can contain pointers to themselves - struct foo { foo *x; } is perfectly fine

So 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

Cat Plus Plus
Cat Plus Plus

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

Related Questions