Talk:Trait
From APIDesign
(Comment provided by jtulach - via ArticleComments extension) |
|||
Line 111: | Line 111: | ||
--[[User:JaroslavTulach|JaroslavTulach]] 07:42, 17 September 2012 (UTC) | --[[User:JaroslavTulach|JaroslavTulach]] 07:42, 17 September 2012 (UTC) | ||
+ | == jtulach said ... == | ||
+ | |||
+ | <div class='commentBlock'> | ||
+ | template<class ITEM> | ||
+ | class llist { | ||
+ | protected: | ||
+ | ITEM *first; | ||
+ | public: | ||
+ | void add(ITEM& p) | ||
+ | { | ||
+ | p.prev=NULL; | ||
+ | p.next=first; | ||
+ | first=&p; | ||
+ | } | ||
+ | void remove(ITEM& p) | ||
+ | { | ||
+ | ITEM *pr=p.prev; | ||
+ | ITEM *nx=p.next; | ||
+ | if(pr) pr->next=nx; | ||
+ | else first=nx; | ||
+ | if(nx) nx->prev=pr; | ||
+ | } | ||
+ | |||
+ | ITEM *get(int idx) | ||
+ | { | ||
+ | ITEM *i=first; | ||
+ | while((i)&&(idx-->0)) | ||
+ | i=i->next; | ||
+ | |||
+ | return i; | ||
+ | } | ||
+ | |||
+ | llist(void) { first=NULL; }; | ||
+ | ~llist() { }; | ||
+ | }; | ||
+ | |||
+ | // not inherited, requires to define next and prev properties | ||
+ | class person_item { | ||
+ | protected: | ||
+ | int age; | ||
+ | const char* name; | ||
+ | |||
+ | public: | ||
+ | person_item* next; | ||
+ | person_item* prev; | ||
+ | |||
+ | person_item (int age, const char* name) { this->age=age; this->name=name; } | ||
+ | }; | ||
+ | |||
+ | |||
+ | template<class ITEM> | ||
+ | class ll_item { | ||
+ | friend llist <ITEM>; | ||
+ | private: | ||
+ | ITEM* next; | ||
+ | ITEM* prev; | ||
+ | }; | ||
+ | |||
+ | // inherited from ll_item template which defines next and prev for me | ||
+ | class animal_item : public ll_item <animal_item>{ | ||
+ | protected: | ||
+ | const char* name; | ||
+ | |||
+ | public: | ||
+ | |||
+ | animal_item(const char* name) : ll_item <animal_item>() { this->name=name; } | ||
+ | }; | ||
+ | |||
+ | int main(int argc, char* argv[]) | ||
+ | { | ||
+ | person_item a(10, "Ben"); | ||
+ | person_item b(20, "Nora"); | ||
+ | person_item c(30, "John"); | ||
+ | |||
+ | animal_item x("Fifi"); | ||
+ | animal_item y("Bobika"); | ||
+ | animal_item z("Bill"); | ||
+ | |||
+ | llist <person_item> persons; | ||
+ | persons.add(b); | ||
+ | persons.add(c); | ||
+ | persons.add(a); | ||
+ | // persons.add(x); - compilation fails | ||
+ | |||
+ | llist <animal_item> animals; | ||
+ | animals.add(x); | ||
+ | animals.add(y); | ||
+ | animals.add(z); | ||
+ | } | ||
+ | |||
+ | |||
+ | |||
+ | --jtulach 14:04, 17 September 2012 (CEST) | ||
+ | </div> |
Revision as of 12:04, 17 September 2012
Comments on Trait <comments />
Contents |
Miles Elam said ...
I am primarily interested in properly typing the multiple class encapsulation case. E.g. having prev/next field in the item class and manipulating them in only by the list. Moreover I'd like to write this (and type this) in a generic way. Looks like it will be possible to do it in C++, but the solution will definitely not be like in Java/Scala - rather upside-down...
--JaroslavTulach 22:25, 5 September 2012 (UTC)
jtulach said ...
The article is more about STL overhead. I personally don't like and don't use STL. I think following C++ implementation is as fast as implementation in C.
class ll_item; class llist { public: void add(ll_item& p); void remove(ll_item& p); llist(void); ~llist(); }; class ll_item { friend llist; private: ll_item* next; ll_item* prev; }; class person_item : public ll_item { protected: int age; const char* name; public: person_item(int age, const char* name); }; class animal_item : public ll_item { protected: const char* name; public: animal_item(const char* name); }; int main(int argc, char* argv[]) { person_item a(10, "Ben"); person_item b(20, "Nora"); person_item c(30, "John"); animal_item x("Fifi"); animal_item y("Bobika"); animal_item z("Bill"); llist l; l.add(b); l.add(c); l.add(a); l.add(x); l.add(y); l.add(z); }
--jtulach 21:07, 11 September 2012 (CEST)
Right, now the question is how to generify this (as the example above does not feel type safe enough) - e.g. how to turn into a C++ template?
I don't want to have list of items, but rather list of persons and another list of animals. When I ask for an item from each list, I would expect I get a person in the former case and an animal in the latter. Right now I just get a ll_item. That is efficient, but not really typesafe.
I have an unfinished prototype with template and compared to Java or Scala it feels a bit upside down. Interesting clash of cultures.
--JaroslavTulach 08:26, 12 September 2012 (UTC)
jtulach said ...
I don't know why do you think this is not type safe? You cannot add anything which is not inherited from ll_item. If you have enabled RTTI, you can dynamic cast items from list.
class persons_list : public llist { public: person_item *get(int idx) { return dynamic_cast<person_item *>(llist::get(idx)); } };
--jtulach 10:58, 13 September 2012 (CEST)
The question Why this is not type-safe? may reveal the progress OOP made during last twenty years. In the middle of nineties Java was claimed a safe language as it could not cause segmentation faults. The C++'s dynamic_cast is a safety of similar kind. It is a runtime check. It is not bad thing, but as I am coding in Java most of the time, I was not calling for something as basic.
When I mentioned type safety, I meant compile type safety. The above dynamic cast does not guarantee that I cannot add animal_item into the llist and try to obtain person_item. To achieve such kind of safety one needs a form of algebraic types (nicely illustrated in the revolutionary essay). Both Scala and Java (since version 1.5) provide a way to marry algebraic types with OOP inheritance and thus provide compile time safety (as code snippets for the direct linked list show).
--JaroslavTulach 07:42, 17 September 2012 (UTC)
jtulach said ...
template<class ITEM> class llist {
protected: ITEM *first; public:
void add(ITEM& p)
{ p.prev=NULL; p.next=first; first=&p; }
void remove(ITEM& p)
{ ITEM *pr=p.prev; ITEM *nx=p.next; if(pr) pr->next=nx; else first=nx; if(nx) nx->prev=pr; }
ITEM *get(int idx) { ITEM *i=first; while((i)&&(idx-->0)) i=i->next;
return i; }
llist(void) { first=NULL; }; ~llist() { };
};
// not inherited, requires to define next and prev properties class person_item {
protected: int age; const char* name;
public: person_item* next; person_item* prev;
person_item (int age, const char* name) { this->age=age; this->name=name; }
};
template<class ITEM>
class ll_item {
friend llist <ITEM>;
private:
ITEM* next; ITEM* prev;
};
// inherited from ll_item template which defines next and prev for me class animal_item : public ll_item <animal_item>{
protected: const char* name;
public:
animal_item(const char* name) : ll_item <animal_item>() { this->name=name; }
};
int main(int argc, char* argv[]) {
person_item a(10, "Ben"); person_item b(20, "Nora"); person_item c(30, "John");
animal_item x("Fifi"); animal_item y("Bobika"); animal_item z("Bill");
llist <person_item> persons; persons.add(b); persons.add(c); persons.add(a);
// persons.add(x); - compilation fails
llist <animal_item> animals; animals.add(x); animals.add(y); animals.add(z);
}
--jtulach 14:04, 17 September 2012 (CEST)
Yes, this is indeed possible in C++ and is, in fact, used extensively in the C++ standard library (aka STL). For a prime example, look no further than std::string or the various pluggable memory allocators. The example given in the article appears to these eyes as one of a C programmer trying to make C++ do things like C and failing. To be more precise, if one is accessing people objects by iterator, why would a raw pointer to a person need to be manipulated in this way? In addition, what happens if the object must be accessed in multiple ways, e.g., exists in both a normal list and a sorted list (or multiple sorted lists). The C method falls down as there is no single pair of *next and *prev but rather multiple.
Don't get me wrong, C definitely has its uses. Its relative simplicity for one. However, C++'s generic algorithms and data structures should not be discarded so lightly.
--Miles Elam 20:26, 4 September 2012 (CEST)