-
How to avoid dynamic_cast/static_cast
Hi,
I've read more than once in this forum (and by people who I believe know what they are talking about), that if your code makes use of dynamic_cast/static_cast it should be redesigned.
Unfortunately my code is full of static_cast and I'd be very curious to understand how I can avoid them. To illustrate, below is some code which is similar to some of the data structures I have in my programs:
Code:
class FSObject
{
public:
virtual bool isFile() const = 0;
virtual bool isFolder() const = 0;
// ...
}
class Folder : public FSObject
{
public:
class iterator;
public:
virtual bool isFile() const { return false; }
virtual bool isFolder() const { return true; }
iterator begin();
iterator end();
// ...
private:
vector<FSObject*> containedItems;
}
class File : public FSObject
{
public:
virtual bool isFile() const { return true; }
virtual bool isFolder() const { return false; }
unsigned long size() const;
// ...
}
The FSObject class represents any file system objects. It is inherited by File and Folder which are specializations to file system objects. A folder, as we all know, can contain file system objects which can be files or folders. The items within a folder can be accessed via an iterator.
So far, so good, I would say it is sensible design. To keep my code as portable as possible, I am not relying on compiler-provided RTTI, but provide the functions ifFile() and isFolder().
Now an example of a function where I have to cast around:
Code:
// Search recursively for a file, for which a call to func returns true
File * recursive_search( Folder * fld, bool (*func)(const File&) )
{
for ( Folder::iterator it = fld.begin(); it != fld.end(); ++it ) {
if ( (*it)->isFolder() ) {
File * result = recursive_search( static_cast<Folder*>(*it), f );
if ( result ) return result;
} else { // (*it)->isFile()
File * file = static_cast<File*>(*it);
if ( func(*file) ) return file;
}
}
return 0;
}
A simple recursive search for a file. As stated above, I'm doing my own rtti using the isF.. functions, therefore I'm using static and not dynamic casts.
Now, what would be a sensible way to avoid the casts? Moving functionality like the iterating to the FSObject class cannot really be the answer.
I'll be happy to read any suggestions, on how to design this better. Note that the code provided here is meant to be illustrative, not complete, optimal or compilable.
-
Re: How to avoid dynamic_cast/static_cast
What can be FSObject be except File or Folder? Why not to drop this design approach and stick more simple one:
Code:
class Record {
enum Type {dir, file, link, ..., etc};
Type type_;
//holds subdirs and files if the current record is a directory oterwise it's empty
vector<Record*> containedItems;
public:
bool isFile() const;
bool isDir() const;
bool isLink() const;
//and so on
};
-
Re: How to avoid dynamic_cast/static_cast
Quote:
Originally Posted by AvDav
What can be FSObject be except File or Folder? Why not to drop this design approach and stick more simple one
Well, why not?
- Bloated objects: Every object will contain much more attributes than it needs and take up more space in memory
- No static type safety: A function that would expect a File object could be passed a Folder object and vice-versa
- Not extensible: Adding an additional object type would require large code modifications
-
Re: How to avoid dynamic_cast/static_cast
Implement AsFile() and AsFolder() virtual functions. Implement the proper one to return "this" and the improper one to throw an exception (in case comeone is stupid and doesnt check first). No casts.
-
Re: How to avoid dynamic_cast/static_cast
Well, your function asks for a pointer to a Folder, but the iterator is setup to provide a reference to a Folder as the default alternate conversion.
Change your function to accept a Folder reference instead and the confusion presented to the compiler is alleviated.
However, I'm not sure that cast COULD compile, because it would cast a Folder to a Folder * - nonsensical UNLESS the Folder has a conversion function to a pointer of itself (which isn't likely).
In fact, the *it should return a Folder, so if the function accepted a Folder &, all would be well.
Despite warnings to the contrary, the casting operators are there for situations where they should be used. Overuse is usually a sign that design should be reconsidered, but that doesn't mean the casting operators should be avoided when situations require it.
Further, RTTI information is automatically part of the build if exceptions are turned on, and exceptions should be turned on. Portability across compilers may be your limiting factor, but maximum portability is not so often a demand (unless you're making cross platform development tools). Portability is more often about operating system and CPU independence than compiler portability, but that's your call, no doubt.
Use and overuse of dynamic casting usually means virtual functions were indicated, but avoided. Dynamic casting has one particular place where it has no replacement - casting in a multiple inheritance hierarchy.
While many shun multiple inheritance, it can be useful to adapt an existing framework you don't own with common behavior. There can be times when you have a common behavior to apply to disparate framework components, and a second parent does this quite well.
However, if containment by that new common base member is desired, the problem exists whereby the pointer must be adjusted. If, for example, you receive a new object that has multiple parents, but you want to store the object according to one of the secondary parents, not the base most parent, the pointer must be adjusted to reflect the position of that secondary parent. A dynamic cast is the only way to do that.
In early versions of the MS VC Compiler (4.2 I think, maybe it was 4.1, whichever was their first to allow MI), the dynamic cast operator couldn't do this math manipulation - it was broken.
That's no longer an issue, of course, but my point is the casting operators exist to be used (but like anything, not abused).
goto, on the other hand, is an assembler like vestigial wart that should have been surgically removed :)
-
Re: How to avoid dynamic_cast/static_cast
Quote:
Originally Posted by treuss
Well, why not?
- Bloated objects: Every object will contain much more attributes than it needs and take up more space in memory
- No static type safety: A function that would expect a File object could be passed a Folder object and vice-versa
- Not extensible: Adding an additional object type would require large code modifications
1. Not much more, only 1 data member, 4-10 bytes :)
2. Thats why, isFile, isXXX() functions for to determine the type
3. Not really large, just item in Type enumeration and 1 isXXType() identifier function, thats all.
-
Re: How to avoid dynamic_cast/static_cast
you use dynamic_cast (or static_cast) because you break typing somewhere by inserting a file or a folder in a general vector. you use static_cast (or dynamic_cast) to recover from the loss.
if you don't want to break strong typing, thing of your folders as a container of :
- files
-other folders
and treat them accordingly.
For instance :
Code:
class FSObject
{
// ...
}
class Folder : public FSObject
{
public:
class file_iterator;
class folder_iterator;
public:
files_iterator files_begin();
files_iterator files_end();
folder_iterator subfolders_begin();
folder_iterator subfolders_end();
// ...
private:
vector<Files*> containedFiles;
vector<Folder*> containedFolders;
}
class File : public FSObject
{
public:
unsigned long size() const;
// ...
}
and then, the calling code doesn't have to check the type, the folder class provides it
Code:
// Search recursively for a file, for which a call to func returns true
File * recursive_search( Folder * fld, bool (*func)(const File&) )
{
for ( Folder::folders_iterator it = fld.folders_begin(); it != fld.folders_end(); ++it )
{
return recursive_search( static_cast<Folder*>(*it), f );
}
for ( Folder::files_iterator it = fld.files_begin(); it != fld.files_end(); ++it )
{
if ( func(**it) ) return file;
}
return 0;
}
don't lose typing if you want to avoid dynamic_cast or testing a virtual function. You once had enough information and dynamic_cast is a way to recoer the information that was _lost_.
-
Re: How to avoid dynamic_cast/static_cast
I think this is better:
Code:
FSObject *recursive_search(FSObject *fld, bool (*func)(const FSObject&));
Now maybe the cast is not necessary.
-
Re: How to avoid dynamic_cast/static_cast
yeah but as i read the code again i think it simply doesn't work.
If a file is a folder you go into the folder directly and retrn the value, so you will only scan the first folder that has no folder. The return statements will then completely overlook the files! the for loop is not executing since the first item will already call return.
Code:
// Search recursively for a file, for which a call to func returns true
File * recursive_search( Folder * fld, bool (*func)(const File&) )
{
for ( Folder::files_iterator it = fld.files_begin(); it != fld.files_end(); ++it )
{
if ( func(**it) ) return file;
}
for ( Folder::folders_iterator it = fld.folders_begin(); it != fld.folders_end(); ++it )
{
File* result = recursive_search( *it, f );
if(result) retur result;
}
return 0;
}
-
Re: How to avoid dynamic_cast/static_cast
Code:
FSObject *recursive_search(FSObject *fld, bool (*func)(const FSObject&))
{
for ( FSObject::iterator it = fld.begin(); it != fld.end(); ++it ) {
if ( (*it)->isFolder() ) {
return recursive_search( *it, f );
} else { // (*it)->isFile()
if ( func(*it) ) return it;
}
}
return 0;
}
-
Re: How to avoid dynamic_cast/static_cast
the begin()/end() is not part of a FSObject but of a Folder.
-
Re: How to avoid dynamic_cast/static_cast
Quote:
Originally Posted by TheCPUWizard
Implement AsFile() and AsFolder() virtual functions. Implement the proper one to return "this" and the improper one to throw an exception (in case comeone is stupid and doesnt check first). No casts.
Nice one. Only backdraw I see, is that another virtual function call would probably cost some CPU time (unless the compiler optimizes very good), whereas the static_cast does not result in any code to be executed.
-
Re: How to avoid dynamic_cast/static_cast
Quote:
Originally Posted by screetch
yeah but as i read the code again i think it simply doesn't work.
Well spotted. I corrected it to behave correctly.
-
Re: How to avoid dynamic_cast/static_cast
Quote:
Originally Posted by screetch
if you don't want to break strong typing, thing of your folders as a container of :
- files
- other folders
I thought about that possibility, but this way I am losing the information in which order the files/folders are sorted. In my concrete program, that is relevant.
In other words, a recursive search using these two vectors would possibly return a different result than the recursive search I describe.
-
Re: How to avoid dynamic_cast/static_cast
ok i didn't think the order was important. I overlook the client code and i thought that you added files that were found to a list so that order wasn't important.
you have to find a way that preserves both strong typing and the order regardless of the type if you want to avoid dynamic_casts.
you can store the index in a separate vector for example : an iterator tells you what's the type to take, you start with files_begin() and folder_begin() simultaneously. Another iterator is type_begin() (returning an enum which tells if the next item is a folder or a type)
Code:
// Search recursively for a file, for which a call to func returns true
File * recursive_search( Folder * fld, bool (*func)(const File&) )
{
Folder::folders_iterator folders = fld.folders_begin();
Folder::files_iterator files = fld.files_begin();
for ( Folder::type_iterator it = fld.types_begin(); it != fld.types_end(); ++it )
{
switch (*it)
{
case FILE:
assert(files_iterator <> fld.files_end());
if(func(**files_iterator)) return *files_iterator;
++files_iterator;
break;
case FOLDER:
assert(folders_iterator <> fld.folders_end());
{
File* result = recursive_search(**folders_iterator);
if(result) return result;
}
++folders_iterator;
break;
default:assert(false); break;
}
}
// here, if you reached the end of types, you must have reached the end of files and folders
assert(files_iterator == fld.files_end());
assert(folders_iterator == fld.folders_end());
return 0;
}
it seems complicated but i'm sure several other operations that work only on files or on folders will be easier to write. When the order matters it's a bit difficult but when order doesn't matter (i think it's often the case) you haven't lost strong typing.
-
Re: How to avoid dynamic_cast/static_cast
Visitors can be useful in situations like this...
-
Re: How to avoid dynamic_cast/static_cast
As Graham mentions you can use the Visitor Pattern to avoid both casting and your own RTTI implementation. Something like this:
Code:
#include <vector>
using namespace std;
// forward declerations
class File;
class Folder;
// visitor interface
class FSObjectVisitor
{
public:
virtual void VisitFile(File* file)
{
/* nop */
}
virtual void VisitFolder(Folder* folder)
{
/* nop */
}
};
class FSObject
{
public:
// this class accepts visitors
virtual void Accept(FSObjectVisitor& visitor) = 0;
};
class Folder : public FSObject
{
public:
typedef vector<FSObject*> FileVector;
typedef FileVector::iterator Iterator;
public:
Iterator begin() { return containedItems.begin(); }
Iterator end() { return containedItems.end(); }
void AddItem(FSObject* pFSObject)
{
containedItems.push_back(pFSObject);
}
void Accept(FSObjectVisitor& visitor)
{
// tell the visitor that she is visiting a folder
visitor.VisitFolder(this);
}
private:
FileVector containedItems;
};
class File : public FSObject
{
public:
void Accept(FSObjectVisitor& visitor)
{
// tell the visitor that she is visiting a file
visitor.VisitFile(this);
}
};
// implement a visitor that searched for a file
class RecursiveSearchVisitor : public FSObjectVisitor
{
private:
RecursiveSearchVisitor()
{
m_pResult = 0;
}
public:
void VisitFile(File* pFile)
{
// for this example we 'match' on the first file found
if (/*some condition*/ true)
{
// it's match
m_pResult = pFile;
}
}
void VisitFolder(Folder* pFolder)
{
// Ok, we're visiting a folder.
// Then iterate over it's 'contained items' and visit them as well
for (Folder::Iterator it = pFolder->begin(); it != pFolder->end(); ++it )
{
// visit item
(*it)->Accept(*this);
// got a match?
if (m_pResult)
break;
}
}
static File* RecursiveSearch(FSObject *pFSObject)
{
RecursiveSearchVisitor pRS;
pFSObject->Accept(pRS);
return pRS.m_pResult;
}
private:
// we store the result here
File* m_pResult;
};
int main(int argc, char* argv[])
{
// create 'root' folder
Folder* pRoot = new Folder();
// add child folders
pRoot->AddItem(new Folder());
pRoot->AddItem(new Folder());
pRoot->AddItem(new Folder());
// add file
pRoot->AddItem(new File());
// do the search
File* pFile = RecursiveSearchVisitor::RecursiveSearch(pRoot);
return 0;
}
- petter
-
Re: How to avoid dynamic_cast/static_cast
Reading this thread, I was also going to suggest the Visitor pattern.
Be aware that having isFile()/isFolder() methods is just as bad as using dynamic_cast<> from a design standpoint. They both result in runtime type checking code.
The Visitor pattern deftly avoid these issues.
Some ruminations:
From an OS standpoint, files and folders are almost the same thing.
From a user standpoint, files and folders are completely different.
I would suggest modeling the user standpoint as it is more natural and easier to think about. To this end, I would completely the base class FSObject, do away with the inheritance, keeping FSFile and FSDirectory completely separate.
A folder is something that contains both folders and other files. This can be modeled rather simply as:
Code:
class FSFile
{
...
};
class FSFolder
{
private:
std::vector<FSFolderPtr> m_folders;
std::vector<FSFilePtr> m_files;
};
Why would you ever need to have a homogeneous collection of files and folders?
Jeff
-
Re: How to avoid dynamic_cast/static_cast
he wanted to keep the order in which files and folders were added.
-
Re: How to avoid dynamic_cast/static_cast
Quote:
Originally Posted by screetch
he wanted to keep the order in which files and folders where added.
timestamp?
Jeff
-
Re: How to avoid dynamic_cast/static_cast
it doesn't change the problem much, you have to order items whether they are files or folders, then treat folders and files differently.
-
Re: How to avoid dynamic_cast/static_cast
Quote:
Originally Posted by screetch
it doesn't change the problem much, you have to order items whether they are files or folders, then treat folders and files differently.
But that's a different problem, with varying solutions that don't sacrifice the fundamental design. The file/folder relationship is simple and obvious, and should be modeled in a simple and obvious manner.
Using inheritance because you want to order objects by some criteria is not sufficient reason to change this. You are basically using inheritance to make heterogeneous objects appear homogeneous.
Even if you want a base class to maintain access permissions, you'd be better making a base class 'HasAccessPermissions' rather than the miscellaneous bucket class that FSObject will surely become.
Even with a common base class, it's cleaner to keep directories and files in separate containers.
Jeff
-
Re: How to avoid dynamic_cast/static_cast
yeah i agree with that, see my post above. I advised to keep separate containers for fils and folders, and an order index which helps sorting them.
Having files and folders sorted is not needed often, that's why I really recommand to keep them typed strongly and have a little overhead to recover the order rather than the opposit.
Abstraction is cool, the visitor stuff is cool too, but you'll probably end up having lots of operations working on only files and folder and you'll have a lot of stuff to visit :-/
-
Re: How to avoid dynamic_cast/static_cast
Thanks Graham for pointing out the visitor pattern and especially wildfrog for the excellent example. Took me some time to grasp the principle, but now it looks like a very elegant way of doing this. However, I am a bit concerned about the overhead, so I will come back after I did some benchmarking of the visitor pattern approach against the static_cast approach.
@ jfaust and screetch: The folder/file thing is meant to be an example. You find tree structures like this in many other places in IT and while you can argue if the order is important in the filesystem, you cannot argue if you are keeping an XML document representation (e.g. DOM) in memory.
There is also a very clear is-a relationship between the items, so breaking the principal pattern of generalization/specialization is certainly going in the wrong direction.
Thanks again everybody for your thoughts.
-
Re: How to avoid dynamic_cast/static_cast
You might also want to look up Composite pattern as an alternative.
It depends what you want to do, which one you use (or use both):
Composite models the situation you have - heterogeneous entities, some of which are, themselves, containers of those entities;
Visitor provides a way of adding functionalilty to a hierarchy without having to add new member functions to every element. The downside of Visitor is that you need a stable hierarchy - adding a new element involves a lot of work, since every visitor has to be updated for the new class.
-
Re: How to avoid dynamic_cast/static_cast
Quote:
Originally Posted by Graham
[...]The downside of Visitor is that you need a stable hierarchy - adding a new element involves a lot of work, since every visitor has to be updated for the new class.
I can't see that as a real downside, at least not compared to my current way of doing things. If I add a new element, currently I would have to manually search throughout my whole code to find each and every "if (x->isFile())" statement. With the visitor pattern, I can make the function for the new element pure virtual and will have the compiler show me all places where my program needs to be extended. And that is really nice.
BTW, wildfrog, is there a reason why you do not make the functions of FSObjectVisitor pure virtual? I would always prefer that, as it forces me to think about each FSObject subclass, whenever I implement a new visitor.
-
Re: How to avoid dynamic_cast/static_cast
Quote:
Originally Posted by treuss
Thanks Graham for pointing out the visitor pattern and especially wildfrog for the excellent example. Took me some time to grasp the principle, but now it looks like a very elegant way of doing this. However, I am a bit concerned about the overhead.
The overhead isn't bad: it's a double-dispatch technique, resulting in two virtual function calls. This is usually not an issue.
Quote:
Originally Posted by treuss
@ jfaust and screetch: The folder/file thing is meant to be an example. You find tree structures like this in many other places in IT and while you can argue if the order is important in the filesystem, you cannot argue if you are keeping an XML document representation (e.g. DOM) in memory.
There is also a very clear is-a relationship between the items, so breaking the principal pattern of generalization/specialization is certainly going in the wrong direction.
Thanks again everybody for your thoughts.
One last recommendation: boost::variant is a simple object that can be one of several heterogeneous types (a file or a folder). These heterogeneous types can then be stored in a vector.
How you communicate with the actual object is through a built-in visitor interface.
I haven't had the oppurtunity to use this yet, but it looks pretty cool. It may be useful in your case.
Jeff
-
Re: How to avoid dynamic_cast/static_cast
Quote:
you cannot argue if you are keeping an XML document representation (e.g. DOM) in memory.
Actually by specification, sibling nodes can be presented in any order within an XML document. If you have a document where the ordering of nodes is required, then it is non-conformant!
-
Re: How to avoid dynamic_cast/static_cast
Quote:
BTW, wildfrog, is there a reason why you do not make the functions of FSObjectVisitor pure virtual? I would always prefer that, as it forces me to think about each FSObject subclass, whenever I implement a new visitor.
What you're saying is probably a good idéa... I was probably thinking of future lazyness.
To have a choice I would probably do something like this:
Code:
// pure visitor interface
class IFSObjectVisitor
{
public:
virtual void VisitFile(File* file) = 0;
virtual void VisitFolder(Folder* folder) = 0;
};
// default implementation does nothing
class DefaultFSObjectVisitor : public IFSObjectVisitor
{
virtual void VisitFile(File* file)
{
}
virtual void VisitFolder(Folder* folder)
{
}
};
Then depending on the mood I would either inherit the pure interface, or the default implementation.
Quote:
Originally Posted by TheCPUWizard
Actually by specification, sibling nodes can be presented in any order within an XML document. If you have a document where the ordering of nodes is required, then it is non-conformant!
Ok, that may be true for attributes, but I can't see how that fits for child elements, processing instructions, comments etc.
What your're saying is that a XHTML document like this:
Code:
...
<body>
<b>This is </b><i>a test</i>
</body>
...
Is equvalent to
Code:
...
<body>
<i>a test</i><b>This is </b>
</body>
...
And that the browser may choose how to render the page? And what about XML based language like VoiceXML, CCXML etc., are those non-conformant?
- petter
-
Re: How to avoid dynamic_cast/static_cast
Quote:
Originally Posted by wildfrog
What you're saying is probably a good idéa...
Definitely a Visitor should have a pure virtual interface. The resulting compile error generated when a new type is added is what separates the Visitor Pattern from type checking. It forces you to consider the new case. Notice that your DefaultFSObjectVisitor does still cause said compiler error.
Now, for the Observer pattern, I use default empty methods since the Observers carefully pick-and-choose what to listen to and often don't care about the other methods (Observers work more like events).
Jeff
-
Re: How to avoid dynamic_cast/static_cast
Visitor solution is okay but it does not look better than having members isDir() and isFile() in that these functions would be making 1 virtual call while the visitor solution makes 2. Good that it hides the keywords "*_cast" but I don't think it is well suited here.
For composite pattern, I looked at it the first time and tried pulling out some code that can help achieve the search functionality but it too required knowledge if a FSObject is a folder or not. Here is what I roughly got (incomplete but then I don't feel like completing it):
Code:
#include <iostream>
#include <string>
#include <vector>
class IFSObject
{
public:
virtual const std::vector<IFSObject*>& GetChildren() const = 0;
virtual const std::string& GetName() const = 0
virtual IFSObject& Clone() = 0;
//other interfaces
virtual ~IFSObject(){}
};
class File : public IFSObject
{
public:
File(const std::string& name_) : name(name_){}
const std::vector<IFSObject*>& GetChildren() const{
return std::vector<IFSObject*>();
}
const std::string& GetName() const{
return name;
}
IFSObject& Clone(){
return *(new File(name));
}
private:
std::string name;
}
class Folder : public IFSObject
{
public:
Folder(const std::string& name_) : name(name_){}
const std::vector<IFSObject*>& GetChildren() const{
return children;
}
void Add(const IFSObject& something){
/*Should keep IFSObject ownership*/
children.push_back(&(something.Clone()));
}
void Remove(const IFSObject& something){
/*Add remove logic*/
}
IFSObject& Clone(){
return *(new Folder(name));
}
~Folder(){
/*Add children cleanup logic*/
}
private:
std::string name;
std::vector<IFSObject*> children; /*may use shared_ptr<T>*/
}
class Link : public IFSObject
{
/*add implementations with extensions*/
}
IFSObject* search(const std::string& name, const Folder& folder)
{
//would need to know the actual type
//Why??
//Because there can be empty folders and links which will not be files.
}
int main()
{
//create a filesystem
Folder folder;
//fill folder with various components
IFSObject* fsobject_ptr = search("somefile", folder);
return 0;
}
Or else, I might not have understood composite pattern well. But the thing is the moment a differentiation is necessary (which I think is), the composite pattern becomes a wrong choice.
Having a variant type might not help as well. Type information seems necessary. A virtual call is necessary.
There is one advantage with composite pattern though - the virtual call to know the type would be only necessary in the worst cases, i.e. when a directory does not have files and the cases when the FSObject may be a link. For the rest of the iteration one can go without knowing what FSObject* actually points to. That is a choice you can make.
I also had a quick look at boost::filesystem library where I though they might be having some different solution when finding files in a filesystem. But, alas, they don't have any such mechanism either. They also need to know the "type" information. They use a function named "is_directory". Take a look at the following code:
Code:
bool find_file( const path & dir_path, // in this directory,
const std::string & file_name, // search for this name,
path & path_found ) // placing path here if found
{
if ( !exists( dir_path ) )
return false;
directory_iterator end_itr; // default construction yields past-the-end
for ( directory_iterator itr( dir_path ); itr != end_itr; ++itr )
{
if ( is_directory( *itr ) )
{
if ( find_file( *itr, file_name, path_found ) ) return true;
}
else if ( itr->leaf() == file_name ) // see below
{
path_found = *itr;
return true;
}
}
return false;
}
So as to conclude my thoughts on this now, there are 2 options: go ahead with what you have already (something as in OP) or use composite pattern with type checking in worst cases. That can be reduced even further with one thing that I am not very sure of - but atleast in *nix and windows doing ls -altr or dir, I always see 2 entries (directories . and ..) may be paths to previous directory and current working directory. This may be a case worth considering whatever you are modelling (I think that is not actually a FS).
Hope it helps a little bit.
-
Re: How to avoid dynamic_cast/static_cast
Quote:
Originally Posted by exterminator
I also had a quick look at boost::filesystem library where I though they might be having some different solution when finding files in a filesystem. But, alas, they don't have any such mechanism either. They also need to know the "type" information. They use a function named "is_directory".
Somehow I don't find the Boost find_file example suitable for comparison (with the OP). They're only working with the type path, which can represent a path to 'anything' (a file, directory, link etc.). More interestingly a single Boost path instance can at one time represent a file, then later change and represent a directory.
- petter
-
Re: How to avoid dynamic_cast/static_cast
Quote:
Originally Posted by wildfrog
Somehow I don't find the Boost find_file example suitable for comparison (with the OP). They're only working with the type path, which can represent a path to 'anything' (a file, directory, link etc.). More interestingly a single Boost path instance can at one time represent a file, then later change and represent a directory.
Yeah, it is not exactly the same, but I found it quite analogous. path can be path to anything, FSObject* can point to anything.
-
Re: How to avoid dynamic_cast/static_cast
Quote:
Originally Posted by exterminator
Yeah, it is not exactly the same, but I found it quite analogous. path can be path to anything, FSObject* can point to anything.
You're right, but a path* will always point to a path and there is never a need for any casting. I would compare it to file/directory attributes. You don't specialize the File class and make a ReadOnlyFile or HiddenAndReadOnlyFile class, instead you add a functions like bool isReadOnly() or int getAttributes() to the File class.
- petter
-
Re: How to avoid dynamic_cast/static_cast
Quote:
Originally Posted by wildfrog
You're right, but a path* will always point to a path and there is never a need for any casting.
.. and that path string will either end up with a directory or a file. And to recursively get into a directory, a check for is_directory is used. There isn't a need of casting because is_directory tells about what path is, if it is a directory path it makes the recursive call else gets to the leaf which would be a file. That is what OP's isFile(), isFolder calls would do without casting. Surely there is a difference as these are dynamic calls but the things being achieved is similar and that is differentiation between files or folders.
Quote:
Originally Posted by wildfrog
I would compare it to file/directory attributes. You don't specialize the File class and make a ReadOnlyFile or HiddenAndReadOnlyFile class, instead you add a functions like bool isReadOnly() or int getAttributes() to the File class.
Well, there can be different approaches. Why can one not derive File to make ROFile/HAROFile/etc? That would cause a very different File class definition with conversions available depending upon user actions. But having member functions like isReadOnly/getAttributes can also be an approach. This can be another discussion on its own.
By the way, I am not quite sure if I understand where you are heading. The example given by me was not the determining point on which my thoughts were based. :)
-
Re: How to avoid dynamic_cast/static_cast
Quote:
Originally Posted by TheCPUWizard
Actually by specification, sibling nodes can be presented in any order within an XML document. If you have a document where the ordering of nodes is required, then it is non-conformant!
This is OT, but you must be kidding. I'm not reading into the XML standard, but if you look at the XML Schema standard, you will see that there are two elements for describing the structure of an XML document: <xs:sequence> and <xs:all>. The former one defines that the order of the child elements matter, the latter one defines that it does not matter. So why would the W3C define an element to specify a document structure that is not according to the standard.
-
Re: How to avoid dynamic_cast/static_cast
docbook is an xml file, order is important....no kidding, order is important, try to change chapters order ^_^
-
Re: How to avoid dynamic_cast/static_cast
Quote:
Originally Posted by jfaust
The overhead isn't bad: it's a double-dispatch technique, resulting in two virtual function calls. This is usually not an issue.
Just as a follow-up: I implemented the visitor pattern into my program and did a quick performance test of the old vs. the visitor implementation. The result is that the visitor solution requires roughly 4% more CPU time for traversing a large data structure. So it's not a big issue, but it is measurable and to save 5% again on other places will be quite difficult. However, it will probably be less than 4% if the search condition will actually do some check as opposed to just returning false as it did for the test.
-
Re: How to avoid dynamic_cast/static_cast
Quote:
Originally Posted by treuss
Just as a follow-up: I implemented the visitor pattern into my program and did a quick performance test of the old vs. the visitor implementation. The result is that the visitor solution requires roughly 4% more CPU time for traversing a large data structure. So it's not a big issue, but it is measurable and to save 5% again on other places will be quite difficult. However, it will probably be less than 4% if the search condition will actually do some check as opposed to just returning false as it did for the test.
Intersting. You must be calling this a lot. I'm assuming that you are profiling on a release build, not a debug build, and running outside the debugger.
Just out of curiousity, what are the actual runtimes. Are we talking milliseconds or seconds here?
Jeff
-
Re: How to avoid dynamic_cast/static_cast
Quote:
Originally Posted by jfaust
Intersting. You must be calling this a lot.
Well, I was just curious, if the difference is measurable.
Quote:
Originally Posted by jfaust
I'm assuming that you are profiling on a release build, not a debug build, and running outside the debugger.
The code is optimized with g++ -O3 -funroll-loops -march=athlon-xp -ftracer, which usually gave me the best results.
Quote:
Originally Posted by jfaust
Just out of curiousity, what are the actual runtimes. Are we talking milliseconds or seconds here?
Depends on how much data you want to process ;). My test was traversing a data structure consisting of 180138 "folders" and 600552 "files" 100 times and the time it took on my Athlon XP 25000 was 7.77 vs 8.07 seconds. So for most people and most applications the difference would be insignificant.