 |
C++Talk.NET C++ language newsgroups
|
| View previous topic :: View next topic |
| Author |
Message |
terry Guest
|
Posted: Thu Nov 16, 2006 8:09 am Post subject: using vector to encapulate a tree - non const copy construct |
|
|
Repost as the original (13th Oct) did not appear on the site.
Hi, I have a problem with the way some implimentations of vector and other
stl containers refuse to use non-const contructors. My question is - is
the way they function the correct interpretation of the standard - and
if so
why.
MOTIVATION
The following data structure
class mytree: public T, protected std::vector<mytree>{};
defines a tree with an object of type T at each node (it is in top down
form where at each node, one has a value of type T, and a container of
offspring
trees).
Of course, one must define copy constructors and assignment operators and
maybe some interesting iterators before it has real value, but still, the
above declaration gives a top down encapsulation for trees.
The problem for me is that, for efficiency, one needs a non-const version
of the assignment and copy constructors. Everyone expects the constructor
mytree(const mytree & rhs)
to be defined, and of course it must do a deep copy, perhaps based on
using std::vector::assign. But one also really needs
mytree(mytree & rhs)
where the constructor copies the T object and swaps the container content
from the rhs into the lhs and so moves the whole tree just by moving the
root.
The rhs has no meaning afterwards but this is what you expect and want in
many circumstances. This constructor will probably use std::vector::swap
to move the content of the container of trees across.
Overall, this can easily be developed to construct an elegant tree as well
as
variant data structures for specific circumstances with good reuse of code.
The STL class manages all data allocation and deletion etc. Insertions are
relatively cheap etc.
The penalties of the deep copy are used only if they are required.
PROBLEM
My problem is that it seems that whenever a controlled sequence is copied or
moved to a new location by the stl container implimentations in microsoft or
g++ configurations the code seems to convert the objects to be copied to
const
reference types before calling the copy constructor.
This is, in my view, a bad decision, as I believe that it is up to the
input_iterator pointing to the data to be moved to decide whether the
controlled sequence is const and then C++ should choose the constructor
appropriately to match the signature presented.
Is this an issue caused by the standard?
Here is an example that, I hope, demonstrates the issue clearly:
//
#include <vector>
class T
{
public:
T(){}
T(const T & a){} //const copy constructor
};
class S
{
public:
S(){}
S(S& a){} //copy constructor
};
int main(int argc, char* argv[])
{
{
std::vector<T> u(10);
T a;
T* pp=&a;
T* p=pp++;
std::vector<T> v(p,pp);
}
{
// std::vector<S> u(10);
//uncomment the line above to produce an error
//even though there is a default constructor
S a;
S* pp=&a;
S* p=pp++;
//std::vector<S> v(p,pp);
//uncomment the line above produces an error
//even though there is a copy constructor
//and p is of type S*
//and so provides write-able access to its target so the provided copy
//constructor could be used
}
return 0;
}
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ] |
|
| Back to top |
|
 |
Le Chaud Lapin Guest
|
Posted: Fri Nov 17, 2006 8:04 am Post subject: Re: using vector to encapulate a tree - non const copy const |
|
|
terry wrote:
| Quote: | Repost as the original (13th Oct) did not appear on the site.
Hi, I have a problem with the way some implimentations of vector and other
stl containers refuse to use non-const contructors. My question is - is
the way they function the correct interpretation of the standard - and
if so
why.
MOTIVATION
The following data structure
class mytree: public T, protected std::vector<mytree>{};
defines a tree with an object of type T at each node (it is in top down
form where at each node, one has a value of type T, and a container of
offspring
trees).
|
As Ulrich pointed out, mytree is not yet anything, and there can be no
containment of something that is a nothing.
The conceptual image you have in mind, trying to make a tree by
successively "spreading" out from a node, is erroneous. You're going to
have to make a full-blow tree structure "in the raw", using internal
linkage, and encapsulate all of it in a class called "mytree".
-Le Chaud Lapin-
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ] |
|
| Back to top |
|
 |
terry Guest
|
Posted: Fri Nov 17, 2006 10:10 am Post subject: Re: using vector to encapulate a tree - non const copy const |
|
|
| Quote: | class mytree: public T, protected std::vector<mytree>{};
This comes up pretty often: vector<> requires a complete type as parameter
but 'mytree' above is not a complete type.
|
I think you are wrong on this - I am pretty sure this is corredct and well
withint he standard.
In particular mytree's size is well defined in much the same way a pointer
is defined.
vector<> encapuslates a pointer to an aray stored on the heap
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ] |
|
| Back to top |
|
 |
terry Guest
|
Posted: Fri Nov 17, 2006 10:10 am Post subject: Re: using vector to encapulate a tree - non const copy const |
|
|
| Quote: | What you need is move semantics actually, which aren't in C++ yet.
|
Surely non-const copy constructors and assignment operators already
have move semantics as they make no promise to preserve the RHS.
mytree(mytree & rhs)
and should move the content of rhs to *this as efficiently as it can. I dont
think we need two ways of doing this do we?
In your language, I believe a carefully written container, within its
internals,
never needs to copy existing data, only move it.
Best regards.
Terry
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ] |
|
| Back to top |
|
 |
terry Guest
|
Posted: Fri Nov 17, 2006 10:10 am Post subject: Re: using vector to encapulate a tree - non const copy const |
|
|
| Quote: | MOTIVATION
The following data structure
class mytree: public T, protected std::vector<mytree>{};
defines a tree with an object of type T at each node (it is in top down
form where at each node, one has a value of type T, and a container of
offspring
trees).
As Ulrich pointed out, mytree is not yet anything, and there can be no
containment of something that is a nothing.
The conceptual image you have in mind, trying to make a tree by
successively "spreading" out from a node, is erroneous. You're going to
have to make a full-blow tree structure "in the raw", using internal
linkage, and encapsulate all of it in a class called "mytree".
-Le Chaud Lapin-
|
I dont understand your comments or those of Ulrich.
The syntax
class T : public: mytemplate<T>
can be perfectly correct C++. Indeed is widely used and the only way to
do certain things. My declaration
class mytree: public T, protected std::vector<mytree>{};
compiles perfectly on all compilers I try, and is, I think, completely
correct C++. It is fully formed (has a size), and not self referential.
The object vector<A> does not contain any object
of type A and should be regarded as similar to a collection of (as yet
unassigned) pointers of type A*. The same sort of object as you would
use if you were constructing a tree by hand.
The declaration really does encapulate a tree and can (I have) be used
effectively. I really don't agree that it is erroneous - it is there! It is
just
dual to the normal presentation. My interest is because there are times
where this way of looking at and managing things is exactly what is needed.
I provided this example as motivation for my question which was about
implimentation of the stl containers constructors which use an
input_iterator.
I had hoped my examples near the end of the original posting would be
clear.
Thanks for the ideas though.
Terry
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ] |
|
| Back to top |
|
 |
Francis Glassborow Guest
|
Posted: Sat Nov 18, 2006 10:10 am Post subject: Re: using vector to encapulate a tree - non const copy const |
|
|
In article <1163778871.426938.244870 (AT) f16g2000cwb (DOT) googlegroups.com>, Le
Chaud Lapin <jaibuduvin (AT) gmail (DOT) com> writes
| Quote: | That's just it. Technically, you do not know how vector<> is
implemented. You certainly cannot infer that it has a collection of
A*, or even pointer to a not-yet-allocated array of A. Suppose I
replace std::vector with
template <typename X> struct Vector
{
X buffer[16];
} ;
While not disagreeing with the rest of your post, this part is wrong. |
The implementation is not allowed to implement vector<> like that, well
not just like that. Without looking at the requirements in detail I
cannot comment as to whether some variant of the small string
optimisation might be allowable.
However the standard does require that given:
std::vector<T> vectorof T;
code making vectorofT non-empty
&vectorofT[0] produces a pointer to the first element of a contiguous
array of T
--
Francis Glassborow ACCU
Author of 'You Can Do It!' and "You Can Program in C++"
see http://www.spellen.org/youcandoit
For project ideas and contributions:
http://www.spellen.org/youcandoit/projects
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ] |
|
| Back to top |
|
 |
terry Guest
|
Posted: Sat Nov 18, 2006 11:23 pm Post subject: Re: using vector to encapulate a tree - non const copy const |
|
|
| Quote: | The notion that a fully contained, assignable, constructible, wieldable
tree is the same as one node of such a tree is conceptually incorrect.
|
If you want to see a commercially implimented example of a fully contained,
assignable, constructible, wieldable tree that is the same as one node of
such a tree look at mathematica:
The basic object in mathematica is an expression. An expression has two
parts
- a head - which can be any type e.g. an operator name such as Plus or List
or even another expression.
and a container of expressions.
Even if a little disguised, this is a labeled tree if ever there was one.
Interestingly, the different containers can be seen as well - as for
example, in the orderless attribute (multi-set) type container.
Indeed mathematics is full of such examples.
Part of the problems you raise come about because there are many situations
where it is not natural to insert additional member trees in a tree and
additional structure is required to make this ok.
For example, suppose that one factored a big number into two, and then each
of these numbers etc, until all the leaves were prime. The whole would
happily correspond to a factoring of the root number, each node further
down
the tree also correponds to an object of the same type.
It is hard to see why one would not use the same C++ type for all the
factored numbers.
This is also a good example to see that sometimes insertion needs extra
structure. For example, inserting one factored number into the
factorisation
of another has little meaning unless all the parent nodes are updated to
produce a new factorisation.
I think a bit of confusion arises because there really are two types of
tree
one needs to think about.
The one with arrows pointing from the node to children and one with arrows
pointing from children towards the parent nodes. The former are
distinguished from the latter by the nature of the destructor. In the
former
case the tree can be destructed in one go by a simple recursion. These are
the objects that naturally containers of themselves.
In second case each extant leaf has to be deleted separately. Insertion
tends to be very natural in this second type (an extra pointer). they are
absolutely bad for container use.
I dont think one should be dogmatic - I have been using containers to
construct and contain complex tree like objects for many years (at least 6)
and they encapulate a lot of useful functionality that I would otherwise
have to re-write.
Best regards
Terry
PS In my world an instance of a parent -> child tree always has at least a
value and an empty container. The default constructor should always produce
the default value and an empty vector. The dual object child->parent is
never a single object and can be empty without a problem.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ] |
|
| Back to top |
|
 |
Francis Glassborow Guest
|
Posted: Mon Nov 20, 2006 10:09 am Post subject: Re: using vector to encapulate a tree - non const copy const |
|
|
In article <O6ednXnwOuuyj8LYRVnyrQ (AT) eclipse (DOT) net.uk>, terry
<news1 (AT) lyonstech (DOT) net> writes
| Quote: | In most implimentations, an object of type vector<A>,
that is to say - the memory allocated and mapped out in the stack
is an array of (three) objects of type A* . I was loosely refering to
this array as a collection - which of course was, in the context, confusing.
|
Where on earth do you get that idea from? Essentially a vector knows
where the contiguous dynamic array is that it has used for the elements,
how many elements there are currently in the array and how much slack it
has for adding new elements.
--
Francis Glassborow ACCU
Author of 'You Can Do It!' and "You Can Program in C++"
see http://www.spellen.org/youcandoit
For project ideas and contributions: http://www.spellen.org/youcandoit/projects
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ] |
|
| Back to top |
|
 |
James Kanze Guest
|
Posted: Tue Nov 21, 2006 1:46 am Post subject: Re: using vector to encapulate a tree - non const copy const |
|
|
terry wrote:
| Quote: | One of the constraints on the first parameter of std::vector
(and in fact, on all type parameters of all templates in the
standard library, at present) is that it be a complete type at
the point of instantiation.
I think this is at the heart of the matter. From what has
been said, the standard does not say at the time of definition
or time of declaration.
|
It says "when instantiating a template component". Definition
or declaration. In fact, in your case, you need a definition;
the incomplete type resulting from a declaration cannot be used
as a base class.
| Quote: | I suggest that whether this type is complete at instantiation
is a more tricky question, and that this type of
declaration/definition provides a good example of the
distinction. (although I accept completely that I might get
it wrong as well.)
Are you sure that, for most implementations, at the time
instantiation using the default constructor for
vector<mytree>, mytree is not a complete type.
|
The constructor has nothing to do with it. To instantiate the
class std::vector<T>, T must be a complete type---std::vector
is a template component of the library, and the quoted text
applies. According to §14.7.1: "Unless a class template
specialization has been explicitly instantiated (14.7.2) or
explicitly specialized (14.7.3), the class template
specialization is implicitly instantiated when the
specialization is referenced in a context that requires a
completely-defined object type or when the completeness of the
class type affects the semantics of the program." To be used as
a base class, a class must be completely-defined.
| Quote: | I find it hard to believe any compiler would know how to
instantiate an incomplete type. Is there a simple example?
|
template< typename > class Toto ;
// ...
Toto< int >* pToto ;
Even if the complete definition of Toto were present, a
completely defined type is not needed here, so the compiler will
not instantiate it.
| Quote: | As I see it, the standard makes it clear that a type can be
incomplete at one point in a compilation unit and complete
later. Self referential declarations are certainly legal and
can define complete types after the event.
|
Certainly. And it is doubtlessly possible to implement
std::vector in a way that the complete type is not required to
instantiate the class template. The standard, however, doesn't
require this of the implementation; an implementation is also
free to implement the template in a way that requires the
complete definition.
By instantiating the template on an incomplete type, you have
violated your end of the contract, and the compiler/library are
no longer required to keep their end. (In practice, of course,
either the code will compile and work, or it won't compile. I
don't think you really have to fear all of the other potential
undefined behaviors.)
| Quote: | The result depends on the template design:
|
The result depends first and foremost on the contract which
constrains the template design.
| Quote: | template class <S> mytemplate
{
S* address;
}
class T : public: mytemplate<T>{};
//T is complete here.
but
template class <S> mytemplate2
{
S value;
}
class T2 : public: mytemplate<T2>{};
//Error T2 very incomplete!!
|
Right.
What the standard effectively says is that an implementation of
std::vector is free to use either of these.
| Quote: | I suspect that for most implimentations of the template
std::vector, the compiler would have mytree available as a
complete type at instantiation time just like T. In this case,
vector<mytree> would be well defined and accord to the
standard (of course one might want new constructors etc. but
that is another story).
As the examples for T and T2 above show, one should expect
that this would be implimentation dependent and one can
certainly construct implimentations of vector that made mytree
self referential and incomplet (typically because vector<T
is declared with a T object in it like T2 above).
|
The standard makes some very careful distinctions.
Implementation dependant means that the implementation must
explictly make a choice, and document it. In this case, the
standard says undefined behavior---the implementation isn't
required to formally make any choice, nor document it. (An
implementation is, of course, free to define any undefined
behavior it wishes, any way it wants. Off hand, I don't know of
any implementation which does define this particular case, but
if the documentation of your implementation does guarantee it,
you can use it. In non-portable code.)
| Quote: | At the risk of being a bit controversial and overstepping my
expertise, I feel that there could be a case for recommending
that the standard requires that for all stl container
implimentations, the definition
class T: std::container< T >{};
// T complete here
is always true.
|
It is clear that in this case, the standard made a simple,
global statement, rather than evaluate each individual case, and
that more guarantees could be given. The next version of the
standard, for example, will explicitly allow the instantiation
of shared_ptr, and maybe some other classes, without a complete
definition being present.
I'm not sure, however, that anyone feels motivated to extend
this sort of guarantee to the containers. Conceptually, a
container contains objects of the instantiation type; an
std::vector<T> is conceptually pretty much the same thing as a
T[]. And a T[] does require a complete type. IMHO, there is no
real motivation for not requiring a complete type.
Note too that you won't get very far talking about inheritance.
It is generally acknowledged that you should not inherit from a
standard container. The same problem is present, however, if
you want to use it as a member:
struct T { std::vector<T > children ; } ;
is not a legal definition. But even here: conceptually, a class
cannot contain instances of itself, and conceptually,
std::vector contains instances of the instantiation type. If
you want simply to navigate, the normal C++ solution is to use
pointers.
--
James Kanze (GABI Software) email:james.kanze (AT) gmail (DOT) com
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ] |
|
| Back to top |
|
 |
Le Chaud Lapin Guest
|
Posted: Thu Nov 23, 2006 8:56 am Post subject: Re: using vector to encapulate a tree - non const copy const |
|
|
Edward Rosten wrote:
| Quote: | template<class A, class B> struct node
{
typedef A left;
typedef B right;
};
struct leaf{};
typedef node<node<leaf, leaf>, leaf> a_tree;
how is a_tree::left any less of a tree than a_tree?
|
typedef node<node<leaf, leaf>, leaf> a_banana;
Behold, it is now a banana! :D
Point, simply chaning a variable names does not justify a conceptual
characterization.
But, as B.L Whorf pointed out, naming is important, which is why this
issue is worth discussing.
| Quote: | It can't contain a complete copy of itself, but it can contain more
trees.
|
We could probably argue about this indefinitely. I could make a C
linked-list and declare that the thing that I use to grab hold of the
list (a pointer) "contains" the list. In fact, there is no containment
physically in any case. It depends on what layer of abstraction is
being used. The OP took advantage of a particular implementation of
vector<> to create linkage in C++ that gave the appearance that there
was containment. It's actually a raw linked list tree, the kind that
would be made in C. I would imagine this will become evident as soon
he starts writing the destructor, etc.
| Quote: | You could have equally well:
struct mytree: public vector<mytree
{
//node data
};
|
| Quote: | Or equally well:
struct mytree
{
//node data goes here
vector<mytree> children;
};
You should also ask yourself what happens when you have a mytree that
has 0 elements in it.
It's a node with no children. It will still have the data assosciated
with the node. Mytree isn't just a vector<mytree>, it's a
vector<mytree> with nodal data.
|
So two question:
1. How many nodes does a 0-element tree have?
2. How many nodes does a 1-element tree have?
| Quote: |
Is it still a mytree?
Yes.
What if the T part of a
mytree (mytreenode) takes up by default construction 16,384 bytes?
Then buy a machine with more RAM
|
I rest my case. [I know you're joking]
| Quote: | The mytree struct is a node. An empty vector<mytree> on its own is an
empty forest.
|
One must concede that an "emtpy" mytree could still take up 100,000
bytes to contain a nothing.
| Quote: | In fact, as I have stated comp.theory, this
directly caused said computer scientists to define the height of a
1-node binary tree to be 0 when the height should be 1.
I really, really don't see how treating a node as a complete tree in
any way leads to that.
|
I speculate that there are some people who implement linked-structures
feel more comfortable holding in hand a node of the linked structure
instead of a pointer to its root element:
struct Node
{
char gigantic_array[1024x768];
int x;
int y;
Node *pointer_to_left, *pointer_to_right;
} ;
Node *pointer_to_root; // better conceptual regularity
Node tree; // worse conceptual regularity and potentially expensive for
0-node "tree".
Unfortunately, IMO, so many computer scientists opted for the latter.
Some even state in their books..."..and a zero-element tree would be
represented by a dummy node whose data would not be used, and there
would be a flag inside the Node, to say whether it is the dummy node."
Compare that to how I would define a binary tree of elements:
template <typename Element> struct Binary_Tree
{
unsigned int population;
struct Node
{
Element element;
Node *pointer_to_left;
Node *pointer_to_right;
} *pointer_to_root;
Binary_Tree & operator = (const Binary_Tree &that);
} ;
A generalized Tree<> has a regular structure also, but I feel every
engineer should have the personal pleasure of discovering it for
himself/herself. :)
Now all the special requirements, dummy-node-for-no-use, is-valid flag,
and worries about the memory penalty of a node goes away.
Finally, I won't derive them here, but some formulas related to binary
trees can be made "prettier" if a 0-element tree is redefined to have
height 0, a 1-element tree is redefined to have height 1, and a perfect
2^N-1 tree is redefined to have height N. In fact, the -1 in (N-1) in
the exponential of many of these formulas has to do with the definition
that a 1-node tree has height 0.
Note that, aside from the prettier formulas and regularity of
implementation, all things seem to be equal. But if that is the case,
then why hold a reservation? Go for the model that is prettier and
provides the simplest implementation.
Also note that, if computer scientists *had* defined the height of a
1-node binary tree to be 1, and I were arguing the opposite, that it
should be 0, they would simply invoke my own argument: "The new model
you propose Mr. Lapin is almost identitical to ours, except ours is
prettier and permits greater simplicity in implementation, so on what
basis do you expect us to change?"
-Le Chaud Lapin-
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ] |
|
| Back to top |
|
 |
Edward Rosten Guest
|
Posted: Sun Nov 26, 2006 5:08 am Post subject: Re: using vector to encapulate a tree - non const copy const |
|
|
Le Chaud Lapin wrote:
| Quote: | Well then! You should be ashamed for making a container that could
not possibly be empty. :> This fact alone should have set off an alarm
that something is not quite right.
|
It's a decision tree. What is the answer to a nonexistent question?
(follow up to comp.lang.c++.philosophy).
I use a forest (ie list<tree>) to store many. This could well be empty.
| Quote: | As for having a flag inside the node to note that it's a dummy node,
well yuck!
is kind of neat since using std::vectot would ensure that this is safe
when it comes to memory management, assignment, copying etc without me
having to write a single constructor/destructor.
There is one more problem with these so-called recursive
representations.
mytree mt;
Now let's say you need to know the count of elements in mt:
unsigned int count = mt.count();
There are two options for implementing count():
1. write a recursive function to count the nodes on every invocation
of count()
2. keep a count of the elements as a member variable of mytree.
|
OK, so you caught me out. I do actually have this:
struct treedata
{
int some_useful_metadata;
int some_more_things;
node tree;
};
//where struct node contains vector<node>
The metadata I have is meaningless without the tree having at least one
node.
| Quote: | Using the method that you and the others have proposed, you'd have to
go with option 1. If not, you'd need to store a count in every mytree
node, and have a flag saying whether a mytree node is the root node.
|
I get the first point about storing a count in every node, but why do
you need a flag?
As an aside, I've been wishing that std::set did indeed store a count
in every node, since then
insert(), remove() and nth_element() would all be O(log N). But that's
another point entirely.
| Quote: | In my tree class, objects may contain anywhere between 2000 and
1,350,000 nodes. Guess with method I am using to determine the
count().
|
It depends on what you're trying to achieve. If you incrementally add
and remove elements, then a single count would do. If you want to graft
a subtree on to a node (without say bothering to rebalance the tree),
then a local count would be more useful. I'm guessing that you have a
single global one.
| Quote: | In any case, in situations like this, there are two ways to go about
finding a regular model.
1) Choose the one that "looks better" and know that it will most likely
be better
2) Choose something else only to find holes in it later.
I save a lot of time by choosing models that look/feel right, without
regard for details.
|
My main reason for choosing the (apparently incorrect) recursive vector
implementation is that I thought that the implementation was trivial
and bug free. Any bottlenecks (which haven't occured yet) could be
fixed later. Otherwise it's premature optimization.
-Ed
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ] |
|
| Back to top |
|
 |
terry Guest
|
Posted: Wed Nov 29, 2006 9:49 am Post subject: Re: using vector to encapulate a tree - non const copy const |
|
|
"James Kanze" <james.kanze (AT) gmail (DOT) com> wrote in message
news:1164011507.979825.12620 (AT) m7g2000cwm (DOT) googlegroups.com...
| Quote: | Note too that you won't get very far talking about inheritance.
It is generally acknowledged that you should not inherit from a
standard container.
|
I am sorry that I have not been able to come back to you over the last week
and thanks very much for the extended and interesting reply.
You make several points and I hope to come back to them in time - but they
have a certain order and it seems natural to first approach your remark that
it is out of fashion, or even dissaproved of to derive from containers.
I agree that there seems to be a predudice against this - but I think it
really is predudice - coming from a historic overinvolvement with
polymorphism. It seems to me that C++ becomes really powerful when one
combines derivation and templates. I find myself wanting to derive from the
map class all the time in my work.
I would argue that one very important (and utterly non-polymorphic!) use of
derivation is to produce distinct classes that have identical functionality.
In particular, this allows type safeness between them.
class A
{
public:
int foo(){}
};
template <int n=0> class B
: private A
{
public:
using A::foo;
};
Then A, B<1> and B<2> have identical public functionality foo, and no
visible base classes but they are distinct classes.
To motivate this example, it might be useful to talk in the language of the
vector class and to loosely imagine that A is std::vector. All instances of
the type std::vector<double> obviously have the same type!! Suppose that
some of these vectors are row vectors and some are column vectors, one could
not build an operator, say operator==() or operator+=() that was typesafe
and which refused to compare row and column vectors.
Now suppose (loosely) that A is the template vector, we could use B<1> to
denote a row vector, and B<2> to be a column vector.
The method described above can be adapted to the real world easily enough
and extra functionality can easily be added. I would be interested in other
efficient approaches to cloning vector<T>. My guess is that those that do
not derive from it are likely to duplicate member functions etc. and be
quite messy. This code should be pretty efficient under most compilers. So
why do the powers that be object.
Best
Terry
Close approximation to the real world solution I use (with variations for
all the STL containers) (sorry about line breaks):
#include <vector>
template <typename T, int id> class my_vector;
// declare the new container class my_vector here so friend functions work
correctly
template <typename T, int id> inline bool operator!= (const typename
my_vector <T,id> & _left, const typename my_vector <T,id> & _right);
//add more comparison operators declarations for >= etc. here so friend
functions work correctly
//
template< class T, int id = 0> class my_vector
: std::vector<T> //private derivation from vector<T>
{
private:
typedef std::vector<T> BASE;
public:
// make member functions of BASE accessible if their references to the
vector object itself are type sensible
using BASE::assign;
using BASE::at;
using BASE::back;
using BASE::begin;
using BASE::capacity;
using BASE::clear;
using BASE::empty;
using BASE::end;
using BASE::erase;
using BASE::front;
using BASE::get_allocator;
using BASE::insert;
using BASE::max_size;
using BASE::pop_back;
using BASE::push_back;
using BASE::rbegin;
using BASE::rend;
using BASE::reserve;
using BASE::resize;
using BASE::size;
//using BASE::swap;
//using BASE::vector;
using BASE::operator[];
using BASE::allocator_type;
using BASE::const_iterator;
using BASE::const_pointer;
using BASE::const_reference;
using BASE::const_reverse_iterator;
using BASE::difference_type;
using BASE::iterator;
using BASE::pointer;
using BASE::reference;
using BASE::reverse_iterator;
using BASE::size_type;
using BASE::value_type;
//keep track of unary constructors as they can take arguments that depend
on the type my_vector
my_vector()
:BASE()
{
}
explicit my_vector(size_type _Count)
:BASE(_Count)
{
}
explicit my_vector(const typename allocator_type& _Al)
:BASE(_Al)
{
}
my_vector(const my_vector& _Right)
:BASE(_Right)
{
}
//use templates for other constructors
template <typename U, typename V>
my_vector(U x, V y)
:BASE(x,y)
{
}
template <typename U, typename V, typename W>
my_vector(U x, V y, W z)
:BASE(x,y,z)
{
}
my_vector & swap ( my_vector & _Right )
{
BASE::swap(static_cast<BASE &>(_Right));
return *this;
}
friend bool operator!= <> (const typename my_vector<T,id> & _left, const
typename my_vector<T,id> & _right);//add more friends
};
template <typename T, int id> inline
bool operator!= (const typename my_vector<T,id> & _left, const typename
my_vector<T,id> & _right)
{
return static_cast< typename my_vector<T,id>::BASE const & >(_left) !=
static_cast<typename my_vector<T,id>::BASE const & >(_right);
}
************************************************************************
// and now a simple (and crudely written) demo to illustrate the template
// my_vector as a container template with vector functionality (except
// it uses the default allocator)
#include <vector>
#include <algorithm>
#include <functional>
#include <iostream>
typedef my_vector<double,1> column_vector_double;
typedef my_vector<double,2> row_vector_double;
typedef my_vector<int> vector_int;
int main(int argc, char* argv[])
{
vector_int a,b,c;
for (int count =0; count != 10 ; count ++) a.push_back(count);
b.assign(a.begin(),a.end());
c.swap(b);
std::cout << "default constructor, push_back, assign, and swap all used
here\n";
if (b==c) std::cout << "comparison templates called on private base
class\n";
vector_int d(a.begin(),a.end());
vector_int e(d);
std::cout << "range and copy constructor used\n";
double val = e[3];
std::cout << "operator[] used\n";
column_vector_double f(a.begin(),a.end());
column_vector_double g(f);
row_vector_double h(a.begin(),a.end()),r;//r(g) would fail - row and
column
are different types
std::cout << "row and column vector with double values constructed\n";
std::cout << "range constructor used from a controlled sequence of
ints\n";
std::cout << "direct copy construction of a row from a column would fail -
different types\n";
std::ostream_iterator<double> dOutCol ( std::cout , "\n" );
std::ostream_iterator<double> dOutRow ( std::cout , " " );
copy(f.begin(),f.end(),dOutCol);
row_vector_double::iterator it1(g.begin()),it2(g.end());
copy(it1,it2,dOutRow);
std::cout << "\nstd::copy used to copy data to ostream without and with
construction of intermediate iterators\n";
return 0;
}
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ] |
|
| Back to top |
|
 |
Dave Harris Guest
|
Posted: Fri Dec 01, 2006 2:42 am Post subject: Re: using vector to encapulate a tree - non const copy const |
|
|
news1 (AT) lyonstech (DOT) net (terry) wrote (abridged):
| Quote: | [...] it seems natural to first approach your remark that
it is out of fashion, or even dissaproved of to derive from containers.
I agree that there seems to be a predudice against this - but I think it
really is predudice - coming from a historic overinvolvement with
polymorphism. It seems to me that C++ becomes really powerful when one
combines derivation and templates. I find myself wanting to derive from
the map class all the time in my work.
|
Inheritance is a very tightly coupled relationship. One danger is that all
the names of the base class are injected into the derived class. Another
is that the base class can call any functions in the derived class, even
private ones, if it happens to declare a virtual function with the same
signature itself.
The upshot is that we generally avoid using inheritance where it is not
necessary, and it's not necessary here. Your example class could be
written with a vector member and forwarding functions instead. Eg:
template<typename T, typename Id>
class my_vector {
typedef std::vector<T> Base;
Base base;
public:
typedef Base::iterator iterator;
typedef Base::const_iterator const_iterator;
bool empty() const { return base.empty(); }
size_t size() const { return base.size(); }
// Scores more forwarding functions.
};
It's slightly more work, but since this template is generic we only need
to do it once so that isn't significant.
Incidently, I replaced the int parameter with a type so you don't have to
mess with allocating numbers to types. Instead of:
| Quote: | typedef my_vector<double,2> row_vector_double;
|
you'd have:
struct row_tag {};
typedef my_vector<double,row_tag> row_vector_double;
-- Dave Harris, Nottingham, UK.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ] |
|
| Back to top |
|
 |
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
|