but I will not accept a standard string class which is just a pure
virtual interface. I hope the above discussion shows why.
1) You are saying that if the implementation is created at runtime,
and its
not on the stack then whoever called the factory to create it has to
remember to return it to the recycler when they are done using it,
and its
nice to have a helper class so this is done automatically. That's all
fine,
tasty, American apple pie, but my perception is that this argument
about
lifetime management is completely orthogonal to discussions about
interfaces
for manipulating the object.
2) Sure, classes that remember to destroy the object when we are done
using
it are nice, and I definitely do use them. However, one has to put
this in
perspective. No matter how many times you wrap it with auto
destroying this
and that you still have the same responsibility. Users will sometimes
want
to have a pointer to it, and therefore whoever called the factory to
create
it has to remember to return it to the recycler when they are done
using it.
Most "users" don't have a problem with that.
3) There is some extended discussion about assignment to the string,
but
since assignment is possible through a pure virtual interface then I
am
having problems determine the relevance of that thread of discussion.
4) You should certainly have freedom to use what works best to store a
string in a field in a record, and if users and databases have similar
requirements then perhaps the same implementation of a string should
be used
by them also (by-the-users is a pretty general term). However, optimal
design will be to use a common pure virtual interface and allow many
different implementations. For example, if the string storage is a C
"string
constant" then a very simple, and fast, implementation is better.
With CA
the storage needs to be CA protocol buffers. I already have a library
for
creating, destroying, and manipulating protocol buffers. My hope is
that a
pure virtual string interface can be used to directly manipulate
strings
residing in protocol buffers.
5) The important distinction is that storage management and
implementation
can be and *should* be application specific (in this context database
storage specific). In contrast, important interfaces like dbPutField,
dbGetField, dbGetLink, and anything else that manipulates strings
should use
an pure virtual interface so that A) these codes do not break if the
string
implementation is changed, and B) codes like CA (and CA interfaced
applications) are free to manage string storage and implementation in
a way
that is optimal for their situation.
6) Lastly (and of less significance), sorry to dredge up the past,
but I
can't help but have a very clear memory of lashings I received at
design
meetings for not following appropriate design methodology. During
EPICS
R3.14 design the concerted opinion of the APS contingent was that the
design
of epicsTimer was unacceptable unless it was changed to be pure
virtual. To
be specific, the APS mandated that the design be changed so that an
epicsTimer must be created by calling a factory method returning a
reference
to a pure virtual interface. I can't say that I didn't benefit from
the
experience :-)
Jeff
-----Original Message-----
From: Andrew Johnson [mailto:[email protected]]
Jeff said that he wanted it to be an interface class.
Let's look at that idea, without worrying about what it actually
looks
like: Assume we have all agreed on a single API for a string class
that
I'll call 'StringInterface,' which is a pure interface class.
I can't instantiate an object of type StringInterface since it's a
pure
interface class, so in order to create and use a string in my code I
would have to define a pointer to a StringInterface, and call a
factory
routine or use some other means to create a concrete object of my
chosen
derived class.
StringInterface *pstring = StringFactory(...);
However because I'm using a pointer, I must remember to release that
object (assume for now that just means deleting it) after I've
finished
with it in order to avoid leaking memory when my pstring goes out of
scope:
delete pstring;
Since I'm a well-educated C++ programmer who understands the RAII
idiom
(Resource Acquisition Is Initialization), I know how to solve the
potential memory leak issue: wrap the pointer inside another class
which
has a destructor that will delete the string for me.
I could use the standard C++ auto_ptr template to wrap and manage a
StringInterface pointer, but I don't want the destructive assignment
behaviour that it would give me.
I decide to create a new class implementing StringInterface that
contains and manages a StringInterface pointer for me. It will
release
the object automatically in its destructor, so no memory leaks. That
class will look something like this:
class SafeString public StringInterface {
private: // Data
StringInterface *psi;
public: // Methods
SafeString(StringInterface *concrete = 0) :
psi(concrete) {}
~SafeString()
{
if (psi) delete psi;
}
SetStringInterface(StringInterface *concrete)
{
if (psi) delete psi;
psi = concrete;
}
// For every method in StringInterface, define
returnType method(...)
{
if (!psi) throw(something...);
psi->method(...);
}
private: // Not implemented:
SafeString(const SafeString& rhs);
SafeString& operator=(const SafeString& rhs);
};
I can now create an instance of a SafeString and know that I can't
leak
memory by forgetting to delete the pointer:
SafeString greeting(StringFactory(...));
SafeString isn't a pure interface class since it contains data, but
the
only behaviour it actually defines for itself is the ability to
manage
that pointer for me. I haven't sacrificed any flexibilty on the
part of
the underlying implementation of StringInterface since I forward all
the
StringInterface methods to the real implementation, I have just made
it
safer to use the StringInterface API.
I'd like to make my SafeString slightly easier to use in a couple of
very common cases:
1. Suppose there's an implementation of StringInterface available
through the StringFactory that takes a C++ 'const char *' literal and
makes it accessible using the StringInterface methods. I decide to
add
a constructor to my SafeString class that explicitly creates one of
those kinds of strings when given a 'const char *':
SafeString(const char *pstr) :
psi(StringFactory(..., pstr)) {}
I can now write code like this:
SafeString greeting("Hello, world!");
which is the same as writing this:
SafeString greeting = "Hello, world!";
2. I can't assign anything to a SafeString at the moment. I declared
the self-assignment operator private in the class definition above in
order to prevent the compiler from creating one for me that would
have
just copied the psi pointer, which is definitely the wrong thing to
do.
Assuming that StringInterface can manipulate a mutable string it
must
provide some way to copy one string to another. I'll change my
class to
add an assigment operator using that method:
SafeString& operator=(const StringInterface& rhs)
{
if (!psi) throw(something...);
// Assuming StringInterface::assign(const StringInterface &)
psi->assign(rhs);
}
This now means I can write this:
SafeString greeting = "Hello";
SafeString reply = StringFactory(...); // something mutable
reply = greeting;
Note that there's no copy constructor for SafeString, so I definitely
can't write this:
SafeString greeting = "Hello";
SafeString reply = greeting; // Link error, not implemented
The copy constructor for reply would have no way to tell what kind of
string I want it to instantiate. If StringInterface provided a
clone()
method I could implement a copy constructor to clone the
StringInterface
object that it's copying, but if it doesn't then the above code will
always have to give a link error. For the purposes of the current
argument I don't really care about that, but it's something to bear
in
mind.
Ok, so I've now defined a class that implements and uses our agreed
StringInterface API. It's exactly the same size as a StringInterface
pointer, but my class is safe from memory leaks and is more friendly
to
users. Which should we encourage EPICS V4 users to develop with -
StringInterface pointers, or my SafeString class?
Now please compare SafeString to the overall shape of the definition
of
EpicsString as found in the Wiki page "V4 Design: epicsTypes" at
http://www.aps.anl.gov/epics/wiki/index.php/V4_Design:
_epicsTypes#epicsStr
ing
I would be happy to discuss changes to (or even a complete
replacement
for) the EpicsBuffer interface which EpicsString was based on, and
changes to the methods of EpicsString itself. We can talk about
whether
an EpicsString IS an EpicsBuffer, HAS an EpicsBuffer or even
IS-IMPLEMENTED-IN-TERMS-OF an EpicsBuffer (EffC++), but I will not
accept a standard string class which is just a pure virtual
interface.
I hope the above discussion shows why.
Jeff, I'll be commenting on your reply in a separate message.
- Andrew
PS: Why aren't we discussing this on core-talk?
--
Podiabombastic: The tendency to shoot oneself in the foot.